PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.1
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.1
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / api / class-schema-endpoint.php

class-schema-endpoint.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.1, at includes/api/class-schema-endpoint.php

1,803 lines 60.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Schema API Endpoints Class
4 *
5 * REST API endpoints for schema markup generation, validation, and management.
6 * Provides comprehensive API access to Schema Management System functionality
7 * with proper authentication, validation, and error handling.
8 *
9 * @package ThinkRank
10 * @subpackage API
11 * @since 1.0.0
12 */
13
14 declare(strict_types=1);
15
16 namespace ThinkRank\API;
17
18 // Prevent direct access
19 if (!defined('ABSPATH')) {
20 exit;
21 }
22
23 use ThinkRank\SEO\Schema_Management_System;
24 use ThinkRank\SEO\Schema_Input_Validator;
25 use ThinkRank\API\Traits\Rate_Limiter;
26 use WP_REST_Controller;
27 use WP_REST_Request;
28 use WP_REST_Response;
29 use WP_Error;
30
31 // Load Rate Limiter trait
32 require_once THINKRANK_PLUGIN_DIR . 'includes/api/traits/trait-rate-limiter.php';
33
34 /**
35 * Schema API Endpoints Class
36 *
37 * Provides REST API endpoints for schema markup operations including
38 * generation, validation, deployment, and performance tracking with
39 * proper authentication and comprehensive error handling.
40 *
41 * @since 1.0.0
42 */
43 class Schema_Endpoint extends WP_REST_Controller {
44
45 use Rate_Limiter;
46
47 /**
48 * Schema Management System instance
49 *
50 * @since 1.0.0
51 * @var Schema_Management_System
52 */
53 private Schema_Management_System $schema_manager;
54
55 /**
56 * Schema Input Validator instance
57 *
58 * @since 1.0.0
59 * @var Schema_Input_Validator
60 */
61 private Schema_Input_Validator $input_validator;
62
63 /**
64 * API namespace
65 *
66 * @since 1.0.0
67 * @var string
68 */
69 protected $namespace = 'thinkrank/v1';
70
71 /**
72 * API resource base
73 *
74 * @since 1.0.0
75 * @var string
76 */
77 protected $rest_base = 'schema';
78
79 /**
80 * Constructor
81 *
82 * @since 1.0.0
83 */
84 public function __construct() {
85 $this->schema_manager = new Schema_Management_System();
86 $this->input_validator = new Schema_Input_Validator();
87 }
88
89 /**
90 * Register API routes
91 *
92 * @since 1.0.0
93 */
94 public function register_routes(): void {
95 // Generate schema markup
96 register_rest_route(
97 $this->namespace,
98 '/' . $this->rest_base . '/generate',
99 [
100 [
101 'methods' => 'POST',
102 'callback' => [$this, 'generate_schema'],
103 'permission_callback' => [$this, 'check_generate_permissions'],
104 'args' => $this->get_generate_schema_args()
105 ]
106 ]
107 );
108
109 // Validate schema markup
110 register_rest_route(
111 $this->namespace,
112 '/' . $this->rest_base . '/validate',
113 [
114 [
115 'methods' => 'POST',
116 'callback' => [$this, 'validate_schema'],
117 'permission_callback' => [$this, 'check_validate_permissions'],
118 'args' => $this->get_validate_schema_args()
119 ]
120 ]
121 );
122
123 // Deploy schema markup
124 register_rest_route(
125 $this->namespace,
126 '/' . $this->rest_base . '/deploy',
127 [
128 [
129 'methods' => 'POST',
130 'callback' => [$this, 'deploy_schema'],
131 'permission_callback' => [$this, 'check_deploy_permissions'],
132 'args' => $this->get_deploy_schema_args()
133 ]
134 ]
135 );
136
137 // Get schema types
138 register_rest_route(
139 $this->namespace,
140 '/' . $this->rest_base . '/types',
141 [
142 [
143 'methods' => 'GET',
144 'callback' => [$this, 'get_schema_types'],
145 'permission_callback' => [$this, 'check_read_permissions']
146 ]
147 ]
148 );
149
150 // Get deployed schemas
151 register_rest_route(
152 $this->namespace,
153 '/' . $this->rest_base . '/deployed',
154 [
155 [
156 'methods' => 'GET',
157 'callback' => [$this, 'get_deployed_schemas'],
158 'permission_callback' => [$this, 'check_read_permissions']
159 ]
160 ]
161 );
162
163 // Get schema for context
164 register_rest_route(
165 $this->namespace,
166 '/' . $this->rest_base . '/(?P<context_type>[a-zA-Z0-9_-]+)/(?P<context_id>\d+)',
167 [
168 [
169 'methods' => 'GET',
170 'callback' => [$this, 'get_context_schema'],
171 'permission_callback' => [$this, 'check_read_permissions'],
172 'args' => [
173 'context_type' => [
174 'required' => true,
175 'type' => 'string',
176 'enum' => ['site', 'post', 'page', 'product']
177 ],
178 'context_id' => [
179 'required' => true,
180 'type' => 'integer',
181 'minimum' => 1
182 ]
183 ]
184 ]
185 ]
186 );
187
188 // Optimize rich snippets
189 register_rest_route(
190 $this->namespace,
191 '/' . $this->rest_base . '/optimize',
192 [
193 [
194 'methods' => 'POST',
195 'callback' => [$this, 'optimize_rich_snippets'],
196 'permission_callback' => [$this, 'check_optimize_permissions'],
197 'args' => $this->get_optimize_schema_args()
198 ]
199 ]
200 );
201
202 // Track schema performance
203 register_rest_route(
204 $this->namespace,
205 '/' . $this->rest_base . '/performance/(?P<context_type>[a-zA-Z0-9_-]+)/(?P<context_id>\d+)',
206 [
207 [
208 'methods' => 'GET',
209 'callback' => [$this, 'get_schema_performance'],
210 'permission_callback' => [$this, 'check_read_permissions'],
211 'args' => [
212 'context_type' => [
213 'required' => true,
214 'type' => 'string',
215 'enum' => ['site', 'post', 'page', 'product']
216 ],
217 'context_id' => [
218 'required' => true,
219 'type' => 'integer',
220 'minimum' => 1
221 ]
222 ]
223 ]
224 ]
225 );
226
227 // Get schema preview
228 register_rest_route(
229 $this->namespace,
230 '/' . $this->rest_base . '/preview',
231 [
232 [
233 'methods' => 'POST',
234 'callback' => [$this, 'get_schema_preview'],
235 'permission_callback' => [$this, 'check_read_permissions'],
236 'args' => $this->get_preview_schema_args()
237 ]
238 ]
239 );
240
241 // Bulk operations
242 register_rest_route(
243 $this->namespace,
244 '/' . $this->rest_base . '/bulk',
245 [
246 [
247 'methods' => 'POST',
248 'callback' => [$this, 'bulk_operations'],
249 'permission_callback' => [$this, 'check_bulk_permissions'],
250 'args' => $this->get_bulk_operations_args()
251 ]
252 ]
253 );
254
255 // Schema settings management
256 register_rest_route(
257 $this->namespace,
258 '/' . $this->rest_base . '/settings',
259 [
260 [
261 'methods' => 'GET',
262 'callback' => [$this, 'get_settings'],
263 'permission_callback' => [$this, 'check_read_permissions']
264 ],
265 [
266 'methods' => 'POST',
267 'callback' => [$this, 'save_settings'],
268 'permission_callback' => [$this, 'check_manage_permissions'],
269 'args' => $this->get_settings_args()
270 ]
271 ]
272 );
273 }
274
275 /**
276 * Generate schema markup
277 *
278 * @since 1.0.0
279 *
280 * @param WP_REST_Request $request Request object
281 * @return WP_REST_Response|WP_Error Response object or error
282 */
283 public function generate_schema(WP_REST_Request $request) {
284 try {
285 $user_id = get_current_user_id();
286
287 // SECURITY: Check rate limits first
288 $rate_limit_check = $this->check_rate_limit('generate_schema', $user_id);
289 if (is_wp_error($rate_limit_check)) {
290 return $rate_limit_check;
291 }
292
293 // SECURITY: Validate user permissions and rate limiting
294 $permission_check = $this->input_validator->validate_user_permissions('generate', $user_id);
295 if (!$permission_check['valid']) {
296 return new WP_Error(
297 'permission_denied',
298 implode(', ', $permission_check['errors']),
299 ['status' => 403]
300 );
301 }
302
303 // SECURITY: Validate and sanitize context parameters with ownership checks
304 $context_type = $request->get_param('context_type');
305 $context_id = $request->get_param('context_id');
306 $context_validation = $this->input_validator->validate_context_parameters($context_type, $context_id, $user_id);
307
308 if (!$context_validation['valid']) {
309 return new WP_Error(
310 'invalid_context',
311 implode(', ', $context_validation['errors']),
312 ['status' => 400]
313 );
314 }
315
316 $context_type = $context_validation['sanitized_data']['context_type'];
317 $context_id = $context_validation['sanitized_data']['context_id'];
318
319 // SECURITY: Validate and sanitize schema types
320 $schema_types = $request->get_param('schema_types') ?? [];
321 if (empty($schema_types) || !is_array($schema_types)) {
322 return new WP_Error(
323 'missing_schema_types',
324 'Schema types array is required',
325 ['status' => 400]
326 );
327 }
328
329 // Sanitize schema types
330 $sanitized_schema_types = [];
331 foreach ($schema_types as $type) {
332 $sanitized_type = sanitize_text_field($type);
333 if (!empty($sanitized_type)) {
334 $sanitized_schema_types[] = $sanitized_type;
335 }
336 }
337
338 if (empty($sanitized_schema_types)) {
339 return new WP_Error(
340 'invalid_schema_types',
341 'No valid schema types provided',
342 ['status' => 400]
343 );
344 }
345
346 // SECURITY: Sanitize options
347 $options = $this->input_validator->sanitize_options($request->get_param('options') ?? []);
348
349 // SECURITY: Sanitize content_data if provided
350 $content_data = $request->get_param('content_data');
351 if ($content_data && is_array($content_data)) {
352 $content_data = [
353 'title' => isset($content_data['title']) ? sanitize_text_field($content_data['title']) : '',
354 'description' => isset($content_data['description']) ? sanitize_textarea_field($content_data['description']) : '',
355 'content' => isset($content_data['content']) ? wp_kses_post($content_data['content']) : '',
356 'word_count' => isset($content_data['word_count']) ? (int) $content_data['word_count'] : 0,
357 'focus_keyword' => isset($content_data['focus_keyword']) ? sanitize_text_field($content_data['focus_keyword']) : '',
358 'post_type' => isset($content_data['post_type']) ? sanitize_text_field($content_data['post_type']) : '',
359 'post_url' => isset($content_data['post_url']) ? esc_url_raw($content_data['post_url']) : ''
360 ];
361
362 // Add content_data to options so schema manager can use it
363 $options['content_data'] = $content_data;
364 }
365
366 // SECURITY: Sanitize schema_form_data if provided
367 $schema_form_data = $request->get_param('schema_form_data');
368 if ($schema_form_data && is_array($schema_form_data)) {
369 // Sanitize all form fields
370 $sanitized_form_data = [];
371 foreach ($schema_form_data as $key => $value) {
372 if (is_string($value)) {
373 $sanitized_form_data[sanitize_key($key)] = sanitize_text_field($value);
374 } elseif (is_array($value)) {
375 // Handle array values (like features, steps, etc.)
376 $sanitized_form_data[sanitize_key($key)] = array_map('sanitize_text_field', $value);
377 } elseif (is_numeric($value)) {
378 $sanitized_form_data[sanitize_key($key)] = floatval($value);
379 }
380 }
381
382 // Add schema_form_data to options so schema manager can use it
383 $options['schema_form_data'] = $sanitized_form_data;
384 }
385
386 // Generate schema markup with sanitized inputs
387 $generation_results = $this->schema_manager->generate_schema_markup(
388 $context_type,
389 $context_id,
390 $sanitized_schema_types,
391 $options
392 );
393
394 return new WP_REST_Response([
395 'success' => true,
396 'data' => $generation_results,
397 'message' => 'Schema markup generated successfully'
398 ], 200);
399
400 } catch (\Exception $e) {
401 return new WP_Error(
402 'generation_failed',
403 'Schema generation failed: ' . $e->getMessage(),
404 ['status' => 500]
405 );
406 }
407 }
408
409 /**
410 * Validate schema markup
411 *
412 * @since 1.0.0
413 *
414 * @param WP_REST_Request $request Request object
415 * @return WP_REST_Response|WP_Error Response object or error
416 */
417 public function validate_schema(WP_REST_Request $request) {
418 try {
419 $user_id = get_current_user_id();
420
421 // SECURITY: Validate user permissions and rate limiting
422 $permission_check = $this->input_validator->validate_user_permissions('validate', $user_id);
423 if (!$permission_check['valid']) {
424 return new WP_Error(
425 'permission_denied',
426 implode(', ', $permission_check['errors']),
427 ['status' => 403]
428 );
429 }
430
431 $schema_data = $request->get_param('schema_data');
432 $schema_type = $request->get_param('schema_type');
433 $options = $request->get_param('options') ?? [];
434
435 // SECURITY: Validate input parameters
436 if (empty($schema_data) || empty($schema_type)) {
437 return new WP_Error(
438 'missing_parameters',
439 'Schema data and type are required',
440 ['status' => 400]
441 );
442 }
443
444 // SECURITY: Sanitize schema type
445 $schema_type = sanitize_text_field($schema_type);
446
447 // SECURITY: Validate and sanitize schema data using input validator
448 if (!is_array($schema_data)) {
449 return new WP_Error(
450 'invalid_schema_data',
451 'Schema data must be an array/object',
452 ['status' => 400]
453 );
454 }
455
456 $input_validation = $this->input_validator->validate_schema_data($schema_data, $schema_type);
457 if (!$input_validation['valid']) {
458 return new WP_Error(
459 'schema_validation_failed',
460 'Schema data validation failed: ' . implode(', ', $input_validation['errors']),
461 [
462 'status' => 400,
463 'validation_errors' => $input_validation['errors'],
464 'validation_warnings' => $input_validation['warnings']
465 ]
466 );
467 }
468
469 // Use sanitized data for validation
470 $sanitized_schema_data = $input_validation['sanitized_data'];
471
472 // SECURITY: Sanitize options
473 $options = $this->input_validator->sanitize_options($options);
474
475 // Validate schema markup with sanitized data
476 $validation_results = $this->schema_manager->validate_schema_markup(
477 $sanitized_schema_data,
478 $schema_type,
479 $options
480 );
481
482 return new WP_REST_Response([
483 'success' => true,
484 'data' => $validation_results,
485 'message' => 'Schema validation completed'
486 ], 200);
487
488 } catch (\Exception $e) {
489 return new WP_Error(
490 'validation_failed',
491 'Schema validation failed: ' . $e->getMessage(),
492 ['status' => 500]
493 );
494 }
495 }
496
497 /**
498 * Deploy schema markup
499 *
500 * @since 1.0.0
501 *
502 * @param WP_REST_Request $request Request object
503 * @return WP_REST_Response|WP_Error Response object or error
504 */
505 public function deploy_schema(WP_REST_Request $request) {
506 try {
507 $user_id = get_current_user_id();
508
509 // SECURITY: Validate user permissions and rate limiting
510 $permission_check = $this->input_validator->validate_user_permissions('deploy', $user_id);
511 if (!$permission_check['valid']) {
512 return new WP_Error(
513 'permission_denied',
514 implode(', ', $permission_check['errors']),
515 ['status' => 403]
516 );
517 }
518
519 // SECURITY: Validate and sanitize context parameters with ownership checks
520 $context_type = $request->get_param('context_type');
521 $context_id = $request->get_param('context_id');
522 $context_validation = $this->input_validator->validate_context_parameters($context_type, $context_id, $user_id);
523
524 if (!$context_validation['valid']) {
525 return new WP_Error(
526 'invalid_context',
527 implode(', ', $context_validation['errors']),
528 ['status' => 400]
529 );
530 }
531
532 $context_type = $context_validation['sanitized_data']['context_type'];
533 $context_id = $context_validation['sanitized_data']['context_id'];
534
535 // SECURITY: Validate schema data
536 $schema_data = $request->get_param('schema_data');
537 if (empty($schema_data) || !is_array($schema_data)) {
538 return new WP_Error(
539 'invalid_schema_data',
540 'Valid schema data array is required',
541 ['status' => 400]
542 );
543 }
544
545 // SECURITY: Validate each schema in the data
546 $sanitized_schema_data = [];
547 foreach ($schema_data as $schema_type => $schema_content) {
548 $schema_type = sanitize_text_field($schema_type);
549
550 if (!is_array($schema_content)) {
551 return new WP_Error(
552 'invalid_schema_content',
553 "Schema content for {$schema_type} must be an array",
554 ['status' => 400]
555 );
556 }
557
558 // Ensure schema has required structure fields before validation
559 if (!isset($schema_content['@type'])) {
560 $schema_content['@type'] = $schema_type;
561 }
562 if (!isset($schema_content['@context'])) {
563 $schema_content['@context'] = 'https://schema.org';
564 }
565
566 $input_validation = $this->input_validator->validate_schema_data($schema_content, $schema_type);
567 if (!$input_validation['valid']) {
568 return new WP_Error(
569 'schema_validation_failed',
570 "Schema validation failed for {$schema_type}: " . implode(', ', $input_validation['errors']),
571 [
572 'status' => 400,
573 'schema_type' => $schema_type,
574 'validation_errors' => $input_validation['errors']
575 ]
576 );
577 }
578
579 $sanitized_schema_data[$schema_type] = $input_validation['sanitized_data'];
580 }
581
582 // SECURITY: Sanitize options
583 $options = $this->input_validator->sanitize_options($request->get_param('options') ?? []);
584
585 // Deploy schema markup with sanitized data
586 $deployment_results = $this->schema_manager->deploy_schema_markup(
587 $context_type,
588 $context_id,
589 $sanitized_schema_data,
590 $options
591 );
592
593 return new WP_REST_Response([
594 'success' => true,
595 'data' => $deployment_results,
596 'message' => 'Schema markup deployed successfully'
597 ], 200);
598
599 } catch (\Exception $e) {
600 return new WP_Error(
601 'deployment_failed',
602 'Schema deployment failed: ' . $e->getMessage(),
603 ['status' => 500]
604 );
605 }
606 }
607
608 /**
609 * Get available schema types
610 *
611 * @since 1.0.0
612 *
613 * @param WP_REST_Request $request Request object
614 * @return WP_REST_Response Response object
615 */
616 public function get_schema_types(WP_REST_Request $request): WP_REST_Response {
617 // Get context parameter to determine which schema types to return
618 $context = $request->get_param('context') ?? 'site';
619
620 // Site-level schema types only (post/page schemas handled by metabox)
621 $site_schema_types = [
622 'Organization' => [
623 'name' => 'Organization',
624 'description' => 'Company or organization information (site-wide)',
625 'context_types' => ['site'],
626 'priority' => 'high'
627 ],
628 'LocalBusiness' => [
629 'name' => 'LocalBusiness',
630 'description' => 'Local businesses and service providers (site-wide)',
631 'context_types' => ['site'],
632 'priority' => 'high'
633 ],
634 'Person' => [
635 'name' => 'Person',
636 'description' => 'Individual person or author information (site-wide)',
637 'context_types' => ['site'],
638 'priority' => 'medium'
639 ],
640 'WebSite' => [
641 'name' => 'WebSite',
642 'description' => 'Website-level information and search functionality',
643 'context_types' => ['site'],
644 'priority' => 'high'
645 ]
646 ];
647
648 // All schema types for metabox context
649 $all_schema_types = [
650 'Article' => [
651 'name' => 'Article',
652 'description' => 'News articles, blog posts, and editorial content',
653 'context_types' => ['post', 'page'],
654 'priority' => 'high'
655 ],
656 'BlogPosting' => [
657 'name' => 'BlogPosting',
658 'description' => 'Blog posts and personal articles',
659 'context_types' => ['post', 'page'],
660 'priority' => 'high'
661 ],
662 'TechnicalArticle' => [
663 'name' => 'TechnicalArticle',
664 'description' => 'Technical documentation and tutorials',
665 'context_types' => ['post', 'page'],
666 'priority' => 'high'
667 ],
668 'NewsArticle' => [
669 'name' => 'NewsArticle',
670 'description' => 'News articles and press releases',
671 'context_types' => ['post', 'page'],
672 'priority' => 'high'
673 ],
674 'ScholarlyArticle' => [
675 'name' => 'ScholarlyArticle',
676 'description' => 'Academic and research articles',
677 'context_types' => ['post', 'page'],
678 'priority' => 'high'
679 ],
680 'Report' => [
681 'name' => 'Report',
682 'description' => 'Reports and analytical content',
683 'context_types' => ['post', 'page'],
684 'priority' => 'medium'
685 ],
686 'HowTo' => [
687 'name' => 'HowTo',
688 'description' => 'Step-by-step instructions and tutorials',
689 'context_types' => ['post', 'page'],
690 'priority' => 'medium'
691 ],
692 'FAQPage' => [
693 'name' => 'FAQPage',
694 'description' => 'Frequently Asked Questions pages',
695 'context_types' => ['page', 'post'],
696 'priority' => 'high'
697 ],
698 'Event' => [
699 'name' => 'Event',
700 'description' => 'Events, conferences, and gatherings',
701 'context_types' => ['post', 'page'],
702 'priority' => 'medium'
703 ],
704 'Product' => [
705 'name' => 'Product',
706 'description' => 'Products for e-commerce and retail',
707 'context_types' => ['product', 'post', 'page'],
708 'priority' => 'critical'
709 ],
710 'SoftwareApplication' => [
711 'name' => 'SoftwareApplication',
712 'description' => 'Software applications and web apps',
713 'context_types' => ['post', 'page'],
714 'priority' => 'high'
715 ]
716 ] + $site_schema_types;
717
718 // Return appropriate schema types based on context
719 $schema_types = ($context === 'metabox') ? $all_schema_types : $site_schema_types;
720
721 return new WP_REST_Response([
722 'success' => true,
723 'data' => $schema_types,
724 'message' => 'Schema types retrieved successfully'
725 ], 200);
726 }
727
728 /**
729 * Get deployed schemas
730 *
731 * @since 1.0.0
732 *
733 * @param WP_REST_Request $request Request object
734 * @return WP_REST_Response|WP_Error Response object or error
735 */
736 public function get_deployed_schemas(WP_REST_Request $request) {
737 try {
738 $context_type = $request->get_param('context_type') ?? 'site';
739 $context_id = $request->get_param('context_id');
740
741 // Convert context_id to int if it's a valid numeric string, otherwise null
742 if ($context_id !== null && is_numeric($context_id)) {
743 $context_id = (int) $context_id;
744 } else {
745 $context_id = null;
746 }
747
748 $deployed_schemas = $this->schema_manager->get_deployed_schemas($context_type, $context_id);
749
750 return new WP_REST_Response([
751 'success' => true,
752 'data' => $deployed_schemas,
753 'message' => 'Deployed schemas retrieved successfully'
754 ], 200);
755
756 } catch (\Exception $e) {
757 return new WP_Error(
758 'deployed_schemas_failed',
759 'Failed to retrieve deployed schemas: ' . $e->getMessage(),
760 ['status' => 500]
761 );
762 }
763 }
764
765 /**
766 * Get schema for specific context
767 *
768 * @since 1.0.0
769 *
770 * @param WP_REST_Request $request Request object
771 * @return WP_REST_Response|WP_Error Response object or error
772 */
773 public function get_context_schema(WP_REST_Request $request) {
774 try {
775 $context_type = $request->get_param('context_type');
776 $context_id = (int) $request->get_param('context_id');
777
778 // Validate context
779 if (!$this->validate_context($context_type, $context_id)) {
780 return new WP_Error(
781 'invalid_context',
782 'Invalid context type or ID provided',
783 ['status' => 400]
784 );
785 }
786
787 // Get schema output data
788 $schema_data = $this->schema_manager->get_output_data($context_type, $context_id);
789
790 return new WP_REST_Response([
791 'success' => true,
792 'data' => $schema_data,
793 'message' => 'Context schema retrieved successfully'
794 ], 200);
795
796 } catch (\Exception $e) {
797 return new WP_Error(
798 'retrieval_failed',
799 'Schema retrieval failed: ' . $e->getMessage(),
800 ['status' => 500]
801 );
802 }
803 }
804
805 /**
806 * Optimize rich snippets
807 *
808 * @since 1.0.0
809 *
810 * @param WP_REST_Request $request Request object
811 * @return WP_REST_Response|WP_Error Response object or error
812 */
813 public function optimize_rich_snippets(WP_REST_Request $request) {
814 try {
815 $user_id = get_current_user_id();
816
817 // SECURITY: Validate user permissions and rate limiting
818 $permission_check = $this->input_validator->validate_user_permissions('optimize', $user_id);
819 if (!$permission_check['valid']) {
820 return new WP_Error(
821 'permission_denied',
822 implode(', ', $permission_check['errors']),
823 ['status' => 403]
824 );
825 }
826
827 $schema_data = $request->get_param('schema_data');
828 $schema_type = $request->get_param('schema_type');
829 $options = $request->get_param('options') ?? [];
830
831 // Validate input
832 if (empty($schema_data) || empty($schema_type)) {
833 return new WP_Error(
834 'missing_parameters',
835 'Schema data and type are required',
836 ['status' => 400]
837 );
838 }
839
840 // Optimize rich snippets
841 $optimization_results = $this->schema_manager->optimize_rich_snippets(
842 $schema_data,
843 $schema_type,
844 $options
845 );
846
847 return new WP_REST_Response([
848 'success' => true,
849 'data' => $optimization_results,
850 'message' => 'Rich snippets optimization completed'
851 ], 200);
852
853 } catch (\Exception $e) {
854 return new WP_Error(
855 'optimization_failed',
856 'Rich snippets optimization failed: ' . $e->getMessage(),
857 ['status' => 500]
858 );
859 }
860 }
861
862 /**
863 * Get schema performance data
864 *
865 * @since 1.0.0
866 *
867 * @param WP_REST_Request $request Request object
868 * @return WP_REST_Response|WP_Error Response object or error
869 */
870 public function get_schema_performance(WP_REST_Request $request) {
871 try {
872 $context_type = $request->get_param('context_type');
873 $context_id = (int) $request->get_param('context_id');
874 $options = $request->get_param('options') ?? [];
875
876 // Validate context
877 if (!$this->validate_context($context_type, $context_id)) {
878 return new WP_Error(
879 'invalid_context',
880 'Invalid context type or ID provided',
881 ['status' => 400]
882 );
883 }
884
885 // Track schema performance
886 $performance_data = $this->schema_manager->track_schema_performance(
887 $context_type,
888 $context_id,
889 $options
890 );
891
892 return new WP_REST_Response([
893 'success' => true,
894 'data' => $performance_data,
895 'message' => 'Schema performance data retrieved successfully'
896 ], 200);
897
898 } catch (\Exception $e) {
899 return new WP_Error(
900 'performance_tracking_failed',
901 'Schema performance tracking failed: ' . $e->getMessage(),
902 ['status' => 500]
903 );
904 }
905 }
906
907 /**
908 * Get schema preview
909 *
910 * @since 1.0.0
911 *
912 * @param WP_REST_Request $request Request object
913 * @return WP_REST_Response|WP_Error Response object or error
914 */
915 public function get_schema_preview(WP_REST_Request $request) {
916 try {
917 $schema_data = $request->get_param('schema_data');
918 $schema_type = $request->get_param('schema_type');
919
920 // Validate input
921 if (empty($schema_data) || empty($schema_type)) {
922 return new WP_Error(
923 'missing_parameters',
924 'Schema data and type are required',
925 ['status' => 400]
926 );
927 }
928
929 // Generate preview
930 $preview_data = $this->generate_preview($schema_data, $schema_type);
931
932 return new WP_REST_Response([
933 'success' => true,
934 'data' => $preview_data,
935 'message' => 'Schema preview generated successfully'
936 ], 200);
937
938 } catch (\Exception $e) {
939 return new WP_Error(
940 'preview_failed',
941 'Schema preview generation failed: ' . $e->getMessage(),
942 ['status' => 500]
943 );
944 }
945 }
946
947 /**
948 * Bulk operations for schema management
949 *
950 * @since 1.0.0
951 *
952 * @param WP_REST_Request $request Request object
953 * @return WP_REST_Response|WP_Error Response object or error
954 */
955 public function bulk_operations(WP_REST_Request $request) {
956 try {
957 $user_id = get_current_user_id();
958
959 // SECURITY: Validate user permissions and rate limiting
960 $permission_check = $this->input_validator->validate_user_permissions('bulk_operations', $user_id);
961 if (!$permission_check['valid']) {
962 return new WP_Error(
963 'permission_denied',
964 implode(', ', $permission_check['errors']),
965 ['status' => 403]
966 );
967 }
968
969 $operation = $request->get_param('operation');
970 $items = $request->get_param('items') ?? [];
971 $options = $request->get_param('options') ?? [];
972
973 // Validate input
974 if (empty($operation) || empty($items)) {
975 return new WP_Error(
976 'missing_parameters',
977 'Operation and items are required',
978 ['status' => 400]
979 );
980 }
981
982 $results = [];
983 $errors = [];
984
985 foreach ($items as $item) {
986 try {
987 switch ($operation) {
988 case 'generate':
989 $result = $this->schema_manager->generate_schema_markup(
990 $item['context_type'],
991 $item['context_id'],
992 $item['schema_types'] ?? [],
993 $options
994 );
995 break;
996 case 'validate':
997 $result = $this->schema_manager->validate_schema_markup(
998 $item['schema_data'],
999 $item['schema_type'],
1000 $options
1001 );
1002 break;
1003 case 'deploy':
1004 $result = $this->schema_manager->deploy_schema_markup(
1005 $item['context_type'],
1006 $item['context_id'],
1007 $item['schema_data'],
1008 $options
1009 );
1010 break;
1011 default:
1012 throw new \Exception("Unsupported operation: {$operation}");
1013 }
1014
1015 $results[] = [
1016 'item' => $item,
1017 'success' => true,
1018 'data' => $result
1019 ];
1020
1021 } catch (\Exception $e) {
1022 $errors[] = [
1023 'item' => $item,
1024 'error' => $e->getMessage()
1025 ];
1026 }
1027 }
1028
1029 return new WP_REST_Response([
1030 'success' => empty($errors),
1031 'data' => [
1032 'results' => $results,
1033 'errors' => $errors,
1034 'total_processed' => count($items),
1035 'successful' => count($results),
1036 'failed' => count($errors)
1037 ],
1038 'message' => "Bulk {$operation} operation completed"
1039 ], 200);
1040
1041 } catch (\Exception $e) {
1042 return new WP_Error(
1043 'bulk_operation_failed',
1044 'Bulk operation failed: ' . $e->getMessage(),
1045 ['status' => 500]
1046 );
1047 }
1048 }
1049
1050 /**
1051 * Permission callbacks
1052 */
1053
1054 /**
1055 * Check permissions for schema generation with CSRF protection
1056 *
1057 * @since 1.0.0
1058 *
1059 * @param WP_REST_Request $request Request object
1060 * @return bool Permission status
1061 */
1062 public function check_generate_permissions(WP_REST_Request $request): bool {
1063 // Check user capability
1064 if (!current_user_can('edit_posts')) {
1065 return false;
1066 }
1067
1068 // SECURITY: Verify nonce for CSRF protection
1069 return $this->verify_request_nonce($request);
1070 }
1071
1072 /**
1073 * Check permissions for schema validation with CSRF protection
1074 *
1075 * @since 1.0.0
1076 *
1077 * @param WP_REST_Request $request Request object
1078 * @return bool Permission status
1079 */
1080 public function check_validate_permissions(WP_REST_Request $request): bool {
1081 // Check user capability
1082 if (!current_user_can('edit_posts')) {
1083 return false;
1084 }
1085
1086 // SECURITY: Verify nonce for CSRF protection
1087 return $this->verify_request_nonce($request);
1088 }
1089
1090 /**
1091 * Check permissions for schema deployment with CSRF protection
1092 *
1093 * @since 1.0.0
1094 *
1095 * @param WP_REST_Request $request Request object
1096 * @return bool Permission status
1097 */
1098 public function check_deploy_permissions(WP_REST_Request $request): bool {
1099 // Check user capability
1100 if (!current_user_can('publish_posts')) {
1101 return false;
1102 }
1103
1104 // SECURITY: Verify nonce for CSRF protection
1105 return $this->verify_request_nonce($request);
1106 }
1107
1108 /**
1109 * Check permissions for reading schema data
1110 *
1111 * @since 1.0.0
1112 *
1113 * @param WP_REST_Request $request Request object
1114 * @return bool Permission status
1115 */
1116 public function check_read_permissions(WP_REST_Request $request): bool {
1117 // Read operations only require basic capability
1118 return current_user_can('read');
1119 }
1120
1121 /**
1122 * Check permissions for schema optimization with CSRF protection
1123 *
1124 * @since 1.0.0
1125 *
1126 * @param WP_REST_Request $request Request object
1127 * @return bool Permission status
1128 */
1129 public function check_optimize_permissions(WP_REST_Request $request): bool {
1130 // Check user capability
1131 if (!current_user_can('edit_posts')) {
1132 return false;
1133 }
1134
1135 // SECURITY: Verify nonce for CSRF protection
1136 return $this->verify_request_nonce($request);
1137 }
1138
1139 /**
1140 * Check permissions for bulk operations with CSRF protection
1141 *
1142 * @since 1.0.0
1143 *
1144 * @param WP_REST_Request $request Request object
1145 * @return bool Permission status
1146 */
1147 public function check_bulk_permissions(WP_REST_Request $request): bool {
1148 // Check user capability
1149 if (!current_user_can('manage_options')) {
1150 return false;
1151 }
1152
1153 // SECURITY: Verify nonce for CSRF protection
1154 return $this->verify_request_nonce($request);
1155 }
1156
1157 /**
1158 * Check permissions for managing schema settings with CSRF protection
1159 *
1160 * @since 1.0.0
1161 *
1162 * @param WP_REST_Request $request Request object
1163 * @return bool Permission status
1164 */
1165 public function check_manage_permissions(WP_REST_Request $request): bool {
1166 // Check user capability
1167 if (!current_user_can('manage_options')) {
1168 return false;
1169 }
1170
1171 // SECURITY: Verify nonce for CSRF protection (only for POST requests)
1172 if ($request->get_method() === 'POST') {
1173 return $this->verify_request_nonce($request);
1174 }
1175
1176 return true;
1177 }
1178
1179 /**
1180 * Helper methods
1181 */
1182
1183 /**
1184 * Verify request nonce for CSRF protection
1185 *
1186 * @since 1.0.0
1187 *
1188 * @param WP_REST_Request $request Request object
1189 * @return bool Whether nonce is valid
1190 */
1191 private function verify_request_nonce(WP_REST_Request $request): bool {
1192 // Get nonce from header (preferred method for REST API)
1193 $nonce = $request->get_header('X-WP-Nonce');
1194
1195 // Fallback to parameter if header not present
1196 if (!$nonce) {
1197 $nonce = $request->get_param('_wpnonce');
1198 }
1199
1200 // Verify nonce
1201 if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) {
1202 return false;
1203 }
1204
1205 return true;
1206 }
1207
1208 /**
1209 * Validate context type and ID
1210 *
1211 * @since 1.0.0
1212 *
1213 * @param string $context_type Context type
1214 * @param int|null $context_id Context ID
1215 * @return bool Validation status
1216 */
1217 private function validate_context(string $context_type, ?int $context_id): bool {
1218 $valid_types = ['site', 'post', 'page', 'product'];
1219
1220 if (!in_array($context_type, $valid_types, true)) {
1221 return false;
1222 }
1223
1224 if ($context_type !== 'site' && (!$context_id || $context_id <= 0)) {
1225 return false;
1226 }
1227
1228 if ($context_id && !get_post($context_id)) {
1229 return false;
1230 }
1231
1232 return true;
1233 }
1234
1235 /**
1236 * Generate schema preview
1237 *
1238 * @since 1.0.0
1239 *
1240 * @param array $schema_data Schema data
1241 * @param string $schema_type Schema type
1242 * @return array Preview data
1243 */
1244 private function generate_preview(array $schema_data, string $schema_type): array {
1245 return [
1246 'rich_snippets' => [
1247 $schema_type => $this->format_rich_snippet_preview($schema_data, $schema_type)
1248 ],
1249 'json_ld' => wp_json_encode($schema_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
1250 'validation_status' => 'pending'
1251 ];
1252 }
1253
1254 /**
1255 * Format rich snippet preview for specific schema type
1256 *
1257 * @since 1.0.0
1258 *
1259 * @param array $schema_data Schema data
1260 * @param string $schema_type Schema type
1261 * @return array Formatted preview data
1262 */
1263 private function format_rich_snippet_preview(array $schema_data, string $schema_type): array {
1264 switch ($schema_type) {
1265 case 'Organization':
1266 return [
1267 'title' => $schema_data['name'] ?? 'Organization Name',
1268 'url' => $schema_data['url'] ?? home_url(),
1269 'description' => $schema_data['description'] ?? 'Organization description',
1270 'additional_info' => $this->format_organization_info($schema_data)
1271 ];
1272
1273 case 'LocalBusiness':
1274 return [
1275 'title' => $schema_data['name'] ?? 'Business Name',
1276 'url' => $schema_data['url'] ?? home_url(),
1277 'description' => $schema_data['description'] ?? 'Business description',
1278 'additional_info' => $this->format_local_business_info($schema_data)
1279 ];
1280
1281 case 'Article':
1282 return [
1283 'title' => $schema_data['headline'] ?? $schema_data['name'] ?? 'Article Title',
1284 'url' => $schema_data['url'] ?? home_url(),
1285 'description' => $schema_data['description'] ?? 'Article description',
1286 'additional_info' => $this->format_article_info($schema_data)
1287 ];
1288
1289 default:
1290 return [
1291 'title' => $schema_data['headline'] ?? $schema_data['name'] ?? 'Title',
1292 'url' => $schema_data['url'] ?? home_url(),
1293 'description' => $schema_data['description'] ?? 'Description',
1294 'additional_info' => ''
1295 ];
1296 }
1297 }
1298
1299 /**
1300 * Extract additional preview information
1301 *
1302 * @since 1.0.0
1303 *
1304 * @param array $schema_data Schema data
1305 * @param string $schema_type Schema type
1306 * @return array Additional information
1307 */
1308 private function extract_preview_info(array $schema_data, string $schema_type): array {
1309 $info = [];
1310
1311 switch ($schema_type) {
1312 case 'Article':
1313 if (isset($schema_data['author']['name'])) {
1314 $info['author'] = $schema_data['author']['name'];
1315 }
1316 if (isset($schema_data['datePublished'])) {
1317 $info['date'] = gmdate('M j, Y', strtotime($schema_data['datePublished']));
1318 }
1319 if (isset($schema_data['wordCount'])) {
1320 $info['word_count'] = $schema_data['wordCount'];
1321 }
1322 break;
1323 case 'Product':
1324 if (isset($schema_data['offers']['price'])) {
1325 $currency = $schema_data['offers']['priceCurrency'] ?? '';
1326 $info['price'] = $currency . $schema_data['offers']['price'];
1327 }
1328 if (isset($schema_data['brand']['name'])) {
1329 $info['brand'] = $schema_data['brand']['name'];
1330 }
1331 if (isset($schema_data['offers']['availability'])) {
1332 $info['availability'] = str_replace('https://schema.org/', '', $schema_data['offers']['availability']);
1333 }
1334 break;
1335 case 'LocalBusiness':
1336 if (isset($schema_data['address']['addressLocality'])) {
1337 $info['location'] = $schema_data['address']['addressLocality'];
1338 }
1339 if (isset($schema_data['telephone'])) {
1340 $info['phone'] = $schema_data['telephone'];
1341 }
1342 break;
1343 }
1344
1345 return $info;
1346 }
1347
1348 /**
1349 * Format organization additional info
1350 *
1351 * @since 1.0.0
1352 *
1353 * @param array $schema_data Schema data
1354 * @return string Formatted info
1355 */
1356 private function format_organization_info(array $schema_data): string {
1357 $info = [];
1358
1359 if (!empty($schema_data['contactPoint']['telephone'])) {
1360 $info[] = '📞 ' . $schema_data['contactPoint']['telephone'];
1361 }
1362
1363 if (!empty($schema_data['contactPoint']['email'])) {
1364 $info[] = '✉️ ' . $schema_data['contactPoint']['email'];
1365 }
1366
1367 if (!empty($schema_data['address']['streetAddress'])) {
1368 $info[] = '📍 ' . $schema_data['address']['streetAddress'];
1369 }
1370
1371 return implode('', $info);
1372 }
1373
1374 /**
1375 * Format local business additional info
1376 *
1377 * @since 1.0.0
1378 *
1379 * @param array $schema_data Schema data
1380 * @return string Formatted info
1381 */
1382 private function format_local_business_info(array $schema_data): string {
1383 $info = [];
1384
1385 // Address
1386 if (!empty($schema_data['address'])) {
1387 $address = $schema_data['address'];
1388 $address_parts = [];
1389
1390 if (!empty($address['streetAddress'])) {
1391 $address_parts[] = $address['streetAddress'];
1392 }
1393 if (!empty($address['addressLocality'])) {
1394 $address_parts[] = $address['addressLocality'];
1395 }
1396
1397 if (!empty($address_parts)) {
1398 $info[] = '📍 ' . implode(', ', $address_parts);
1399 }
1400 }
1401
1402 // Phone
1403 if (!empty($schema_data['telephone'])) {
1404 $info[] = '📞 ' . $schema_data['telephone'];
1405 }
1406
1407 // Opening hours
1408 if (!empty($schema_data['openingHours'])) {
1409 $hours = is_array($schema_data['openingHours'])
1410 ? implode(', ', $schema_data['openingHours'])
1411 : $schema_data['openingHours'];
1412 $info[] = '🕒 ' . $hours;
1413 }
1414
1415 return implode('', $info);
1416 }
1417
1418 /**
1419 * Format article additional info
1420 *
1421 * @since 1.0.0
1422 *
1423 * @param array $schema_data Schema data
1424 * @return string Formatted info
1425 */
1426 private function format_article_info(array $schema_data): string {
1427 $info = [];
1428
1429 if (!empty($schema_data['author']['name'])) {
1430 $info[] = '👤 By ' . $schema_data['author']['name'];
1431 }
1432
1433 if (!empty($schema_data['datePublished'])) {
1434 $info[] = '�
1435 ' . gmdate('M j, Y', strtotime($schema_data['datePublished']));
1436 }
1437
1438 if (!empty($schema_data['publisher']['name'])) {
1439 $info[] = '🏢 ' . $schema_data['publisher']['name'];
1440 }
1441
1442 return implode(' ', $info);
1443 }
1444
1445 /**
1446 * Argument validation methods
1447 */
1448
1449 /**
1450 * Get arguments for schema generation endpoint
1451 *
1452 * @since 1.0.0
1453 *
1454 * @return array Arguments array
1455 */
1456 private function get_generate_schema_args(): array {
1457 return [
1458 'context_type' => [
1459 'required' => true,
1460 'type' => 'string',
1461 'enum' => ['site', 'post', 'page', 'product'],
1462 'description' => 'Context type for schema generation'
1463 ],
1464 'context_id' => [
1465 'required' => false,
1466 'type' => 'integer',
1467 'minimum' => 1,
1468 'description' => 'Context ID (not required for site context)'
1469 ],
1470 'schema_types' => [
1471 'required' => false,
1472 'type' => 'array',
1473 'items' => [
1474 'type' => 'string',
1475 'enum' => [
1476 'Article', 'BlogPosting', 'TechnicalArticle', 'NewsArticle',
1477 'ScholarlyArticle', 'Report', 'Product', 'Organization',
1478 'LocalBusiness', 'Person', 'WebSite', 'FAQPage',
1479 'Event', 'HowTo', 'SoftwareApplication'
1480 ]
1481 ],
1482 'description' => 'Schema types to generate'
1483 ],
1484 'options' => [
1485 'required' => false,
1486 'type' => 'object',
1487 'description' => 'Additional generation options'
1488 ],
1489 'content_data' => [
1490 'required' => false,
1491 'type' => 'object',
1492 'description' => 'Custom content data to use for schema generation (overrides post data)',
1493 'properties' => [
1494 'title' => ['type' => 'string'],
1495 'description' => ['type' => 'string'],
1496 'content' => ['type' => 'string'],
1497 'focus_keyword' => ['type' => 'string'],
1498 'post_type' => ['type' => 'string'],
1499 'post_url' => ['type' => 'string']
1500 ]
1501 ]
1502 ];
1503 }
1504
1505 /**
1506 * Get arguments for schema validation endpoint
1507 *
1508 * @since 1.0.0
1509 *
1510 * @return array Arguments array
1511 */
1512 private function get_validate_schema_args(): array {
1513 return [
1514 'schema_data' => [
1515 'required' => true,
1516 'type' => 'object',
1517 'description' => 'Schema data to validate'
1518 ],
1519 'schema_type' => [
1520 'required' => true,
1521 'type' => 'string',
1522 'enum' => [
1523 'Article', 'BlogPosting', 'TechnicalArticle', 'NewsArticle',
1524 'ScholarlyArticle', 'Report', 'Product', 'Organization',
1525 'LocalBusiness', 'Person', 'WebSite', 'WebPage', 'FAQPage',
1526 'SoftwareApplication', 'Event', 'Recipe', 'HowTo'
1527 ],
1528 'description' => 'Schema type'
1529 ],
1530 'options' => [
1531 'required' => false,
1532 'type' => 'object',
1533 'description' => 'Validation options'
1534 ]
1535 ];
1536 }
1537
1538 /**
1539 * Get arguments for schema deployment endpoint
1540 *
1541 * @since 1.0.0
1542 *
1543 * @return array Arguments array
1544 */
1545 private function get_deploy_schema_args(): array {
1546 return [
1547 'context_type' => [
1548 'required' => true,
1549 'type' => 'string',
1550 'enum' => ['site', 'post', 'page', 'product'],
1551 'description' => 'Context type for deployment'
1552 ],
1553 'context_id' => [
1554 'required' => false,
1555 'type' => 'integer',
1556 'minimum' => 1,
1557 'description' => 'Context ID (not required for site context)'
1558 ],
1559 'schema_data' => [
1560 'required' => true,
1561 'type' => 'object',
1562 'description' => 'Schema data to deploy'
1563 ],
1564 'options' => [
1565 'required' => false,
1566 'type' => 'object',
1567 'description' => 'Deployment options'
1568 ]
1569 ];
1570 }
1571
1572 /**
1573 * Get arguments for schema optimization endpoint
1574 *
1575 * @since 1.0.0
1576 *
1577 * @return array Arguments array
1578 */
1579 private function get_optimize_schema_args(): array {
1580 return [
1581 'schema_data' => [
1582 'required' => true,
1583 'type' => 'object',
1584 'description' => 'Schema data to optimize'
1585 ],
1586 'schema_type' => [
1587 'required' => true,
1588 'type' => 'string',
1589 'enum' => [
1590 'Article', 'BlogPosting', 'Product', 'Organization', 'LocalBusiness',
1591 'Person', 'WebSite', 'WebPage', 'FAQPage', 'SoftwareApplication',
1592 'BreadcrumbList', 'Event', 'Recipe', 'HowTo'
1593 ],
1594 'description' => 'Schema type'
1595 ],
1596 'options' => [
1597 'required' => false,
1598 'type' => 'object',
1599 'description' => 'Optimization options'
1600 ]
1601 ];
1602 }
1603
1604 /**
1605 * Get arguments for schema preview endpoint
1606 *
1607 * @since 1.0.0
1608 *
1609 * @return array Arguments array
1610 */
1611 private function get_preview_schema_args(): array {
1612 return [
1613 'schema_data' => [
1614 'required' => true,
1615 'type' => 'object',
1616 'description' => 'Schema data to preview'
1617 ],
1618 'schema_type' => [
1619 'required' => true,
1620 'type' => 'string',
1621 'enum' => [
1622 'Article', 'BlogPosting', 'Product', 'Organization', 'LocalBusiness',
1623 'Person', 'WebSite', 'WebPage', 'FAQPage', 'SoftwareApplication',
1624 'BreadcrumbList', 'Event', 'Recipe', 'HowTo'
1625 ],
1626 'description' => 'Schema type'
1627 ]
1628 ];
1629 }
1630
1631 /**
1632 * Get arguments for bulk operations endpoint
1633 *
1634 * @since 1.0.0
1635 *
1636 * @return array Arguments array
1637 */
1638 private function get_bulk_operations_args(): array {
1639 return [
1640 'operation' => [
1641 'required' => true,
1642 'type' => 'string',
1643 'enum' => ['generate', 'validate', 'deploy'],
1644 'description' => 'Bulk operation type'
1645 ],
1646 'items' => [
1647 'required' => true,
1648 'type' => 'array',
1649 'items' => [
1650 'type' => 'object'
1651 ],
1652 'description' => 'Items to process in bulk'
1653 ],
1654 'options' => [
1655 'required' => false,
1656 'type' => 'object',
1657 'description' => 'Bulk operation options'
1658 ]
1659 ];
1660 }
1661
1662 /**
1663 * Get schema settings
1664 *
1665 * @since 1.0.0
1666 *
1667 * @param WP_REST_Request $request Request object
1668 * @return WP_REST_Response|WP_Error Response object or error
1669 */
1670 public function get_settings(WP_REST_Request $request) {
1671 try {
1672 $context_type = $request->get_param('context_type') ?? 'site';
1673 $context_id = $request->get_param('context_id') ?? null;
1674
1675 // Get settings from schema manager
1676 $settings = $this->schema_manager->get_settings($context_type, $context_id);
1677
1678 return new WP_REST_Response([
1679 'success' => true,
1680 'data' => [
1681 'settings' => $settings,
1682 'context_type' => $context_type,
1683 'context_id' => $context_id
1684 ],
1685 'message' => 'Schema settings retrieved successfully'
1686 ], 200);
1687
1688 } catch (\Exception $e) {
1689 return new WP_Error(
1690 'settings_fetch_failed',
1691 'Failed to retrieve schema settings: ' . $e->getMessage(),
1692 ['status' => 500]
1693 );
1694 }
1695 }
1696
1697 /**
1698 * Save schema settings
1699 *
1700 * @since 1.0.0
1701 *
1702 * @param WP_REST_Request $request Request object
1703 * @return WP_REST_Response|WP_Error Response object or error
1704 */
1705 public function save_settings(WP_REST_Request $request) {
1706 try {
1707 $settings = $request->get_param('settings');
1708 $context_type = $request->get_param('context_type') ?? 'site';
1709 $context_id = $request->get_param('context_id') ?? null;
1710
1711 // Validate input parameters
1712 if (empty($settings) || !is_array($settings)) {
1713 return new WP_Error(
1714 'invalid_settings',
1715 'Settings parameter is required and must be an array',
1716 ['status' => 400]
1717 );
1718 }
1719
1720 // Get validation results for detailed error reporting
1721 $validation = $this->schema_manager->validate_settings($settings);
1722
1723 if (!$validation['valid']) {
1724 // Schema settings validation failed - details available in validation response
1725
1726 return new WP_Error(
1727 'validation_failed',
1728 'Schema settings validation failed',
1729 [
1730 'status' => 400,
1731 'validation_errors' => $validation['errors'],
1732 'validation_warnings' => $validation['warnings'] ?? [],
1733 'validation_suggestions' => $validation['suggestions'] ?? []
1734 ]
1735 );
1736 }
1737
1738 // Save settings using schema manager
1739 $success = $this->schema_manager->save_settings($context_type, $context_id, $settings);
1740
1741 if (!$success) {
1742 // Schema settings save failed - database operation unsuccessful
1743
1744 return new WP_Error(
1745 'settings_save_failed',
1746 'Failed to save schema settings to database',
1747 ['status' => 500]
1748 );
1749 }
1750
1751 return new WP_REST_Response([
1752 'success' => true,
1753 'data' => [
1754 'settings' => $settings,
1755 'context_type' => $context_type,
1756 'context_id' => $context_id,
1757 'validation' => $validation
1758 ],
1759 'message' => 'Schema settings saved successfully'
1760 ], 200);
1761
1762 } catch (\Exception $e) {
1763 // Schema settings save exception - error details in response
1764
1765 return new WP_Error(
1766 'settings_update_failed',
1767 'Failed to update schema settings: ' . $e->getMessage(),
1768 ['status' => 500]
1769 );
1770 }
1771 }
1772
1773 /**
1774 * Get arguments for settings endpoints
1775 *
1776 * @since 1.0.0
1777 *
1778 * @return array Arguments array
1779 */
1780 private function get_settings_args(): array {
1781 return [
1782 'settings' => [
1783 'required' => true,
1784 'type' => 'object',
1785 'description' => 'Schema settings object'
1786 ],
1787 'context_type' => [
1788 'required' => false,
1789 'type' => 'string',
1790 'default' => 'site',
1791 'enum' => ['site', 'post', 'page', 'product'],
1792 'description' => 'Context type for settings'
1793 ],
1794 'context_id' => [
1795 'required' => false,
1796 'type' => 'integer',
1797 'minimum' => 1,
1798 'description' => 'Context ID for settings'
1799 ]
1800 ];
1801 }
1802 }
1803