PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.10.0
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-site-identity-endpoint.php

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

1,207 lines 38.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Site Identity API Endpoints Class
4 *
5 * REST API endpoints for site identity management including title formats,
6 * breadcrumb configuration, robots.txt generation, AI-powered site identity
7 * optimization, and global SEO defaults. Provides comprehensive API access to
8 * Site Identity Manager and AI Manager functionality with proper authentication,
9 * validation, and error handling.
10 *
11 * @package ThinkRank
12 * @subpackage API
13 * @since 1.0.0
14 */
15
16 declare(strict_types=1);
17
18 namespace ThinkRank\API;
19
20 use ThinkRank\SEO\Site_Identity_Manager;
21 use ThinkRank\AI\Manager as AI_Manager;
22 use ThinkRank\API\Traits\CSRF_Protection;
23 use WP_REST_Controller;
24 use WP_REST_Request;
25 use WP_REST_Response;
26 use WP_Error;
27
28 // Load CSRF Protection trait
29 require_once THINKRANK_PLUGIN_DIR . 'includes/api/traits/trait-csrf-protection.php';
30
31 /**
32 * Site Identity API Endpoints Class
33 *
34 * Provides REST API endpoints for site identity operations including
35 * title generation, breadcrumb management, robots.txt configuration,
36 * AI-powered site identity optimization, and rule-based optimization
37 * with proper authentication and validation.
38 *
39 * @since 1.0.0
40 */
41 class Site_Identity_Endpoint extends WP_REST_Controller {
42 use CSRF_Protection;
43
44 /**
45 * Site Identity Manager instance
46 *
47 * @since 1.0.0
48 * @var Site_Identity_Manager
49 */
50 private Site_Identity_Manager $identity_manager;
51
52 /**
53 * API namespace
54 *
55 * @since 1.0.0
56 * @var string
57 */
58 protected $namespace = 'thinkrank/v1';
59
60 /**
61 * API resource base
62 *
63 * @since 1.0.0
64 * @var string
65 */
66 protected $rest_base = 'site-identity';
67
68 /**
69 * AI Manager instance
70 *
71 * @since 1.0.0
72 * @var AI_Manager|null
73 */
74 private ?AI_Manager $ai_manager = null;
75
76 /**
77 * Constructor
78 *
79 * @since 1.0.0
80 */
81 public function __construct() {
82 // Ensure Site Identity Manager is loaded
83 if (!class_exists('ThinkRank\\SEO\\Site_Identity_Manager')) {
84 require_once THINKRANK_PLUGIN_DIR . 'includes/seo/class-site-identity-manager.php';
85 }
86
87 $this->identity_manager = new Site_Identity_Manager();
88 }
89
90 /**
91 * Get AI Manager instance (lazy loading)
92 *
93 * @since 1.0.0
94 * @return AI_Manager
95 */
96 private function get_ai_manager(): AI_Manager {
97 if ($this->ai_manager === null) {
98 // Try to get from main plugin container first
99 $plugin_instance = \ThinkRank::get_instance();
100 $this->ai_manager = $plugin_instance->get_component('ai');
101
102 // Fallback to direct instantiation if container fails
103 if ($this->ai_manager === null) {
104 $this->ai_manager = new AI_Manager();
105 }
106 }
107
108 return $this->ai_manager;
109 }
110
111 /**
112 * Register API routes
113 *
114 * @since 1.0.0
115 */
116 public function register_routes(): void {
117 // Get site identity settings
118 register_rest_route(
119 $this->namespace,
120 '/' . $this->rest_base . '/settings',
121 [
122 [
123 'methods' => 'GET',
124 'callback' => [$this, 'get_settings'],
125 'permission_callback' => [$this, 'check_permissions']
126 ],
127 [
128 'methods' => 'POST',
129 'callback' => [$this, 'update_settings'],
130 'permission_callback' => [$this, 'check_permissions'],
131 'args' => $this->get_settings_args()
132 ]
133 ]
134 );
135
136 // Generate title with template
137 register_rest_route(
138 $this->namespace,
139 '/' . $this->rest_base . '/title/generate',
140 [
141 [
142 'methods' => 'POST',
143 'callback' => [$this, 'generate_title'],
144 'permission_callback' => [$this, 'check_permissions'],
145 'args' => $this->get_title_generation_args()
146 ]
147 ]
148 );
149
150 // Get title templates
151 register_rest_route(
152 $this->namespace,
153 '/' . $this->rest_base . '/title/templates',
154 [
155 [
156 'methods' => 'GET',
157 'callback' => [$this, 'get_title_templates'],
158 'permission_callback' => [$this, 'check_permissions']
159 ]
160 ]
161 );
162
163 // Generate breadcrumbs
164 register_rest_route(
165 $this->namespace,
166 '/' . $this->rest_base . '/breadcrumbs/generate',
167 [
168 [
169 'methods' => 'POST',
170 'callback' => [$this, 'generate_breadcrumbs'],
171 'permission_callback' => [$this, 'check_permissions'],
172 'args' => $this->get_breadcrumb_generation_args()
173 ]
174 ]
175 );
176
177 // Get breadcrumb types
178 register_rest_route(
179 $this->namespace,
180 '/' . $this->rest_base . '/breadcrumbs/types',
181 [
182 [
183 'methods' => 'GET',
184 'callback' => [$this, 'get_breadcrumb_types'],
185 'permission_callback' => [$this, 'check_permissions']
186 ]
187 ]
188 );
189
190 // Robots.txt management
191 register_rest_route(
192 $this->namespace,
193 '/' . $this->rest_base . '/robots',
194 [
195 [
196 'methods' => 'GET',
197 'callback' => [$this, 'get_robots_txt'],
198 'permission_callback' => [$this, 'check_read_permissions']
199 ],
200 [
201 'methods' => 'POST',
202 'callback' => [$this, 'update_robots_txt'],
203 'permission_callback' => [$this, 'check_csrf_permissions'],
204 'args' => $this->get_robots_txt_args()
205 ]
206 ]
207 );
208
209 // Site identity optimization (rule-based)
210 register_rest_route(
211 $this->namespace,
212 '/' . $this->rest_base . '/optimize',
213 [
214 [
215 'methods' => 'POST',
216 'callback' => [$this, 'optimize_site_identity'],
217 'permission_callback' => [$this, 'check_permissions'],
218 'args' => $this->get_optimization_args()
219 ]
220 ]
221 );
222
223 // AI-powered site identity optimization
224 register_rest_route(
225 $this->namespace,
226 '/' . $this->rest_base . '/ai-optimize-info',
227 [
228 [
229 'methods' => 'POST',
230 'callback' => [$this, 'ai_optimize_site_info'],
231 'permission_callback' => [$this, 'check_permissions'],
232 'args' => $this->get_ai_optimization_args()
233 ]
234 ]
235 );
236
237 // AI-powered hero content optimization
238 register_rest_route(
239 $this->namespace,
240 '/' . $this->rest_base . '/ai-optimize-hero',
241 [
242 [
243 'methods' => 'POST',
244 'callback' => [$this, 'ai_optimize_hero_content'],
245 'permission_callback' => [$this, 'check_permissions'],
246 'args' => $this->get_hero_optimization_args()
247 ]
248 ]
249 );
250
251 // Validate site identity settings
252 register_rest_route(
253 $this->namespace,
254 '/' . $this->rest_base . '/validate',
255 [
256 [
257 'methods' => 'POST',
258 'callback' => [$this, 'validate_identity_settings'],
259 'permission_callback' => [$this, 'check_permissions'],
260 'args' => $this->get_validation_args()
261 ]
262 ]
263 );
264
265
266
267
268 }
269
270 /**
271 * Get site identity settings
272 *
273 * @since 1.0.0
274 *
275 * @param WP_REST_Request $request Request object
276 * @return WP_REST_Response Response object
277 */
278 public function get_settings(WP_REST_Request $request): WP_REST_Response {
279 try {
280 $context_type = $request->get_param('context_type') ?? 'site';
281 $context_id = $request->get_param('context_id');
282
283 // Get settings from Site Identity Manager
284 $settings = $this->identity_manager->get_settings($context_type, $context_id);
285
286 // Get settings schema for validation
287 $schema = $this->identity_manager->get_settings_schema($context_type);
288
289 return new WP_REST_Response([
290 'success' => true,
291 'data' => [
292 'settings' => $settings,
293 'schema' => $schema,
294 'context_type' => $context_type,
295 'context_id' => $context_id
296 ],
297 'message' => 'Site identity settings retrieved successfully'
298 ], 200);
299
300 } catch (\Exception $e) {
301 return new WP_REST_Response([
302 'success' => false,
303 'error' => 'Failed to retrieve settings: ' . $e->getMessage()
304 ], 500);
305 }
306 }
307
308 /**
309 * Update site identity settings
310 *
311 * @since 1.0.0
312 *
313 * @param WP_REST_Request $request Request object
314 * @return WP_REST_Response|WP_Error Response object or error
315 */
316 public function update_settings(WP_REST_Request $request) {
317 try {
318 $settings = $request->get_param('settings');
319 $context_type = $request->get_param('context_type') ?? 'site';
320 $context_id = $request->get_param('context_id');
321
322 // Validate settings
323 if (empty($settings) || !is_array($settings)) {
324 return new WP_Error(
325 'invalid_settings',
326 'Settings must be provided as an array',
327 ['status' => 400]
328 );
329 }
330
331 // Validate settings using Site Identity Manager
332 $validation = $this->identity_manager->validate_settings($settings);
333
334 if (!$validation['valid']) {
335 return new WP_Error(
336 'validation_failed',
337 'Settings validation failed',
338 [
339 'status' => 400,
340 'validation_errors' => $validation['errors'],
341 'validation_warnings' => $validation['warnings']
342 ]
343 );
344 }
345
346 // Update settings
347 $update_result = $this->identity_manager->save_settings($context_type, $context_id, $settings);
348
349 if (!$update_result) {
350 return new WP_Error(
351 'update_failed',
352 'Failed to update site identity settings',
353 ['status' => 500]
354 );
355 }
356
357 // Get updated settings
358 $updated_settings = $this->identity_manager->get_settings($context_type, $context_id);
359
360 return new WP_REST_Response([
361 'success' => true,
362 'data' => [
363 'settings' => $updated_settings,
364 'validation' => $validation,
365 'context_type' => $context_type,
366 'context_id' => $context_id
367 ],
368 'message' => 'Site identity settings updated successfully'
369 ], 200);
370
371 } catch (\Exception $e) {
372 return new WP_Error(
373 'update_failed',
374 'Settings update failed: ' . $e->getMessage(),
375 ['status' => 500]
376 );
377 }
378 }
379
380 /**
381 * Generate title using template
382 *
383 * @since 1.0.0
384 *
385 * @param WP_REST_Request $request Request object
386 * @return WP_REST_Response|WP_Error Response object or error
387 */
388 public function generate_title(WP_REST_Request $request) {
389 try {
390 $template_name = $request->get_param('template_name') ?? 'default';
391 $data = $request->get_param('data') ?? [];
392 $context = $request->get_param('context') ?? 'site';
393
394 // Generate title using Site Identity Manager
395 $generated_title = $this->identity_manager->generate_title($template_name, $data, $context);
396
397 // Get available templates for reference
398 $templates = $this->get_available_title_templates();
399
400 return new WP_REST_Response([
401 'success' => true,
402 'data' => [
403 'generated_title' => $generated_title,
404 'template_used' => $template_name,
405 'context' => $context,
406 'input_data' => $data,
407 'available_templates' => $templates
408 ],
409 'message' => 'Title generated successfully'
410 ], 200);
411
412 } catch (\Exception $e) {
413 return new WP_Error(
414 'title_generation_failed',
415 'Title generation failed: ' . $e->getMessage(),
416 ['status' => 500]
417 );
418 }
419 }
420
421 /**
422 * Get available title templates
423 *
424 * @since 1.0.0
425 *
426 * @param WP_REST_Request $request Request object
427 * @return WP_REST_Response Response object
428 */
429 public function get_title_templates(WP_REST_Request $request): WP_REST_Response {
430 $templates = $this->get_available_title_templates();
431
432 return new WP_REST_Response([
433 'success' => true,
434 'data' => [
435 'templates' => $templates,
436 'total_templates' => count($templates)
437 ],
438 'message' => 'Title templates retrieved successfully'
439 ], 200);
440 }
441
442 /**
443 * Generate breadcrumbs
444 *
445 * @since 1.0.0
446 *
447 * @param WP_REST_Request $request Request object
448 * @return WP_REST_Response|WP_Error Response object or error
449 */
450 public function generate_breadcrumbs(WP_REST_Request $request) {
451 try {
452 $breadcrumb_type = $request->get_param('breadcrumb_type') ?? 'hierarchical';
453 $options = $request->get_param('options') ?? [];
454
455 // Generate breadcrumbs using Site Identity Manager
456 $breadcrumbs = $this->identity_manager->generate_breadcrumbs($breadcrumb_type, $options);
457
458 // Get available breadcrumb types for reference
459 $types = $this->get_available_breadcrumb_types();
460
461 return new WP_REST_Response([
462 'success' => true,
463 'data' => [
464 'breadcrumbs' => $breadcrumbs,
465 'breadcrumb_type' => $breadcrumb_type,
466 'options' => $options,
467 'available_types' => $types
468 ],
469 'message' => 'Breadcrumbs generated successfully'
470 ], 200);
471
472 } catch (\Exception $e) {
473 return new WP_Error(
474 'breadcrumb_generation_failed',
475 'Breadcrumb generation failed: ' . $e->getMessage(),
476 ['status' => 500]
477 );
478 }
479 }
480
481 /**
482 * Get available breadcrumb types
483 *
484 * @since 1.0.0
485 *
486 * @param WP_REST_Request $request Request object
487 * @return WP_REST_Response Response object
488 */
489 public function get_breadcrumb_types(WP_REST_Request $request): WP_REST_Response {
490 $types = $this->get_available_breadcrumb_types();
491
492 return new WP_REST_Response([
493 'success' => true,
494 'data' => [
495 'breadcrumb_types' => $types,
496 'total_types' => count($types)
497 ],
498 'message' => 'Breadcrumb types retrieved successfully'
499 ], 200);
500 }
501
502 /**
503 * Get robots.txt configuration
504 *
505 * @since 1.0.0
506 *
507 * @param WP_REST_Request $request Request object
508 * @return WP_REST_Response Response object
509 */
510 public function get_robots_txt(WP_REST_Request $request): WP_REST_Response {
511 try {
512 $custom_rules = $request->get_param('custom_rules') ?? [];
513
514 // Generate robots.txt using Site Identity Manager
515 $robots_data = $this->identity_manager->generate_robots_txt($custom_rules);
516
517 return new WP_REST_Response([
518 'success' => true,
519 'data' => $robots_data,
520 'message' => 'Robots.txt data retrieved successfully'
521 ], 200);
522
523 } catch (\Exception $e) {
524 return new WP_REST_Response([
525 'success' => false,
526 'error' => 'Failed to retrieve robots.txt: ' . $e->getMessage()
527 ], 500);
528 }
529 }
530
531 /**
532 * Update robots.txt configuration
533 *
534 * @since 1.0.0
535 *
536 * @param WP_REST_Request $request Request object
537 * @return WP_REST_Response|WP_Error Response object or error
538 */
539 public function update_robots_txt(WP_REST_Request $request) {
540 try {
541 // Check rate limiting
542 if (!$this->check_robots_rate_limit()) {
543 return new WP_Error(
544 'rate_limit_exceeded',
545 'Too many requests. Please wait a few minutes before trying again.',
546 ['status' => 429]
547 );
548 }
549 $custom_rules = $request->get_param('custom_rules') ?? [];
550 $enable_management = $request->get_param('enable_management') ?? true;
551
552 // Validate custom rules format
553 if (!is_array($custom_rules)) {
554 return new WP_Error(
555 'invalid_rules',
556 'Custom rules must be provided as an array',
557 ['status' => 400]
558 );
559 }
560
561 // Generate and validate robots.txt
562 $robots_data = $this->identity_manager->generate_robots_txt($custom_rules);
563
564 if (!empty($robots_data['validation']['errors'])) {
565 return new WP_Error(
566 'validation_failed',
567 'Robots.txt validation failed',
568 [
569 'status' => 400,
570 'validation_errors' => $robots_data['validation']['errors']
571 ]
572 );
573 }
574
575 // Update robots.txt settings
576 $settings = [
577 'robots_txt_enabled' => $enable_management,
578 'custom_robots_rules' => $custom_rules,
579 'robots_txt_content' => $robots_data['content']
580 ];
581
582 $update_result = $this->identity_manager->save_settings('site', null, $settings);
583
584 if (!$update_result) {
585 return new WP_Error(
586 'update_failed',
587 'Failed to update robots.txt settings',
588 ['status' => 500]
589 );
590 }
591
592 // Write robots.txt file to filesystem if management is enabled
593 $file_write_result = ['success' => false, 'message' => 'File writing disabled'];
594 if ($enable_management && !empty($robots_data['content'])) {
595 $write_to_file = $request->get_param('write_to_file') ?? true;
596
597 if ($write_to_file) {
598 $file_write_result = $this->identity_manager->write_robots_txt(
599 $robots_data['content']
600 );
601 }
602 }
603
604 return new WP_REST_Response([
605 'success' => true,
606 'data' => [
607 'robots_data' => $robots_data,
608 'settings_updated' => $settings,
609 'file_write_result' => $file_write_result
610 ],
611 'message' => $file_write_result['success']
612 ? 'Robots.txt configuration updated and file written successfully'
613 : 'Robots.txt configuration updated (file not written: ' . $file_write_result['message'] . ')'
614 ], 200);
615
616 } catch (\Exception $e) {
617 return new WP_Error(
618 'update_failed',
619 'Robots.txt update failed: ' . $e->getMessage(),
620 ['status' => 500]
621 );
622 }
623 }
624
625 /**
626 * Optimize site identity (rule-based)
627 *
628 * @since 1.0.0
629 *
630 * @param WP_REST_Request $request Request object
631 * @return WP_REST_Response|WP_Error Response object or error
632 */
633 public function optimize_site_identity(WP_REST_Request $request) {
634 try {
635 $identity_data = $request->get_param('identity_data');
636 $options = $request->get_param('options') ?? [];
637
638 // Validate identity data
639 if (empty($identity_data) || !is_array($identity_data)) {
640 return new WP_Error(
641 'invalid_data',
642 'Identity data must be provided as an array',
643 ['status' => 400]
644 );
645 }
646
647 // Optimize site identity using Site Identity Manager with options
648 $optimization_results = $this->identity_manager->optimize_site_identity($identity_data, $options);
649
650 return new WP_REST_Response([
651 'success' => true,
652 'data' => $optimization_results,
653 'message' => 'Site identity optimization completed'
654 ], 200);
655
656 } catch (\Exception $e) {
657 return new WP_Error(
658 'optimization_failed',
659 'Site identity optimization failed: ' . $e->getMessage(),
660 ['status' => 500]
661 );
662 }
663 }
664
665 /**
666 * AI optimize site information
667 *
668 * @since 1.0.0
669 *
670 * @param WP_REST_Request $request Request object
671 * @return WP_REST_Response|WP_Error Response object or error
672 */
673 public function ai_optimize_site_info(WP_REST_Request $request) {
674 try {
675 $site_data = $request->get_param('site_data');
676 $options = [
677 'business_type' => $request->get_param('business_type'),
678 'target_audience' => $request->get_param('target_audience'),
679 'tone' => $request->get_param('tone')
680 ];
681
682 // Validate site data
683 if (empty($site_data) || !is_array($site_data)) {
684 return new WP_Error(
685 'invalid_data',
686 'Site data must be provided as an array',
687 ['status' => 400]
688 );
689 }
690
691 // Validate required site data fields
692 $required_fields = ['site_name', 'site_description', 'tagline'];
693 foreach ($required_fields as $field) {
694 if (empty($site_data[$field])) {
695 return new WP_Error(
696 'missing_field',
697 "Required field '{$field}' is missing or empty",
698 ['status' => 400]
699 );
700 }
701 }
702
703 // Sanitize site data
704 $sanitized_site_data = [
705 'site_name' => sanitize_text_field($site_data['site_name']),
706 'site_description' => sanitize_textarea_field($site_data['site_description']),
707 'tagline' => sanitize_text_field($site_data['tagline']),
708 'default_meta_description' => sanitize_textarea_field($site_data['default_meta_description'] ?? '')
709 ];
710
711 // Sanitize options
712 $sanitized_options = [
713 'business_type' => sanitize_text_field($options['business_type'] ?? 'website'),
714 'target_audience' => sanitize_text_field($options['target_audience'] ?? 'general'),
715 'tone' => sanitize_text_field($options['tone'] ?? 'professional')
716 ];
717
718 // Get AI manager and perform optimization
719 $ai_manager = $this->get_ai_manager();
720 $optimization_results = $ai_manager->optimize_site_identity($sanitized_site_data, $sanitized_options);
721
722 return new WP_REST_Response([
723 'success' => true,
724 'data' => $optimization_results,
725 'message' => 'Site identity AI optimization completed'
726 ], 200);
727
728 } catch (\Exception $e) {
729 return new WP_Error(
730 'ai_optimization_failed',
731 'AI optimization failed: ' . $e->getMessage(),
732 ['status' => 500]
733 );
734 }
735 }
736
737 /**
738 * AI-powered hero content optimization
739 *
740 * @since 1.0.0
741 *
742 * @param WP_REST_Request $request Request object
743 * @return WP_REST_Response|WP_Error Response object or error
744 */
745 public function ai_optimize_hero_content(WP_REST_Request $request) {
746 try {
747 $hero_data = $request->get_param('hero_data');
748 $context = $request->get_param('context') ?? [];
749 $options = [
750 'business_type' => $request->get_param('business_type'),
751 'target_audience' => $request->get_param('target_audience'),
752 'tone' => $request->get_param('tone')
753 ];
754
755 // Validate hero data
756 if (empty($hero_data) || !is_array($hero_data)) {
757 return new WP_Error(
758 'invalid_data',
759 'Hero data must be provided as an array',
760 ['status' => 400]
761 );
762 }
763
764 // Sanitize hero data
765 $sanitized_hero_data = [
766 'hero_title' => sanitize_text_field($hero_data['hero_title'] ?? ''),
767 'hero_subtitle' => sanitize_textarea_field($hero_data['hero_subtitle'] ?? ''),
768 'hero_cta_text' => sanitize_text_field($hero_data['hero_cta_text'] ?? ''),
769 'hero_cta_url' => esc_url_raw($hero_data['hero_cta_url'] ?? '')
770 ];
771
772 // Sanitize context data
773 $sanitized_context = [
774 'site_name' => sanitize_text_field($context['site_name'] ?? ''),
775 'site_url' => esc_url_raw($context['site_url'] ?? ''),
776 'business_type' => sanitize_text_field($context['business_type'] ?? ''),
777 'site_description' => sanitize_textarea_field($context['site_description'] ?? '')
778 ];
779
780 // Sanitize options
781 $sanitized_options = [
782 'business_type' => sanitize_text_field($options['business_type'] ?? 'website'),
783 'target_audience' => sanitize_text_field($options['target_audience'] ?? 'general'),
784 'tone' => sanitize_text_field($options['tone'] ?? 'professional'),
785 'context' => $sanitized_context
786 ];
787
788 // Get AI manager and perform optimization
789 $ai_manager = $this->get_ai_manager();
790 $optimization_results = $ai_manager->optimize_homepage_hero($sanitized_hero_data, $sanitized_options);
791
792 return new WP_REST_Response([
793 'success' => true,
794 'data' => $optimization_results,
795 'message' => 'Hero content AI optimization completed'
796 ], 200);
797
798 } catch (\Exception $e) {
799 return new WP_Error(
800 'ai_optimization_failed',
801 'Hero AI optimization failed: ' . $e->getMessage(),
802 ['status' => 500]
803 );
804 }
805 }
806
807 /**
808 * Validate site identity settings
809 *
810 * @since 1.0.0
811 *
812 * @param WP_REST_Request $request Request object
813 * @return WP_REST_Response Response object
814 */
815 public function validate_identity_settings(WP_REST_Request $request): WP_REST_Response {
816 try {
817 $settings = $request->get_param('settings');
818 $tab_context = $request->get_param('tab_context') ?? '';
819
820 // Validate settings using Site Identity Manager with tab context
821 $validation = $this->identity_manager->validate_settings($settings ?? [], $tab_context);
822
823 return new WP_REST_Response([
824 'success' => true,
825 'data' => $validation,
826 'message' => 'Settings validation completed'
827 ], 200);
828
829 } catch (\Exception $e) {
830 return new WP_REST_Response([
831 'success' => false,
832 'error' => 'Validation failed: ' . $e->getMessage()
833 ], 500);
834 }
835 }
836
837
838
839
840
841 /**
842 * Permission callbacks
843 */
844
845 /**
846 * Check permissions for site identity operations (admin-only)
847 *
848 * @since 1.0.0
849 *
850 * @return bool Permission status
851 */
852 public function check_permissions(): bool {
853 return current_user_can('manage_options');
854 }
855
856 /**
857 * Helper methods
858 */
859
860 /**
861 * Validate context type and ID
862 *
863 * @since 1.0.0
864 *
865 * @param string $context_type Context type
866 * @param int|null $context_id Context ID
867 * @return bool Validation status
868 */
869 private function validate_context(string $context_type): bool {
870 // Only support 'site' context in development stage
871 return $context_type === 'site';
872 }
873
874 /**
875 * Get available title templates
876 *
877 * @since 1.0.0
878 *
879 * @return array Title templates
880 */
881 private function get_available_title_templates(): array {
882 return [
883 'default' => [
884 'name' => 'Default',
885 'template' => '%title% %separator% %sitename%',
886 'description' => 'Standard title format with site name'
887 ]
888 ];
889 }
890
891 /**
892 * Get available breadcrumb types
893 *
894 * @since 1.0.0
895 *
896 * @return array Breadcrumb types
897 */
898 private function get_available_breadcrumb_types(): array {
899 return [
900 'hierarchical' => [
901 'name' => 'Hierarchical',
902 'description' => 'Based on page hierarchy and categories'
903 ]
904 ];
905 }
906
907 /**
908 * Argument validation methods
909 */
910
911 /**
912 * Get arguments for settings endpoints
913 *
914 * @since 1.0.0
915 *
916 * @return array Arguments array
917 */
918 private function get_settings_args(): array {
919 return [
920 'settings' => [
921 'required' => true,
922 'type' => 'object',
923 'description' => 'Site identity settings to update'
924 ],
925 'context_type' => [
926 'required' => false,
927 'type' => 'string',
928 'enum' => ['site'],
929 'default' => 'site',
930 'description' => 'Context type (site only)'
931 ],
932 'context_id' => [
933 'required' => false,
934 'type' => 'integer',
935 'minimum' => 1,
936 'description' => 'Context ID (not required for site context)'
937 ]
938 ];
939 }
940
941 /**
942 * Get arguments for title generation endpoint
943 *
944 * @since 1.0.0
945 *
946 * @return array Arguments array
947 */
948 private function get_title_generation_args(): array {
949 return [
950 'template_name' => [
951 'required' => false,
952 'type' => 'string',
953 'enum' => ['default', 'simple', 'reverse', 'category', 'author'],
954 'default' => 'default',
955 'description' => 'Title template to use'
956 ],
957 'data' => [
958 'required' => false,
959 'type' => 'object',
960 'description' => 'Data for placeholder replacement'
961 ],
962 'context' => [
963 'required' => false,
964 'type' => 'string',
965 'enum' => ['site'],
966 'default' => 'site',
967 'description' => 'Context (site only)'
968 ]
969 ];
970 }
971
972 /**
973 * Get arguments for breadcrumb generation endpoint
974 *
975 * @since 1.0.0
976 *
977 * @return array Arguments array
978 */
979 private function get_breadcrumb_generation_args(): array {
980 return [
981 'breadcrumb_type' => [
982 'required' => false,
983 'type' => 'string',
984 'enum' => ['hierarchical', 'taxonomy', 'path', 'custom'],
985 'default' => 'hierarchical',
986 'description' => 'Type of breadcrumb navigation to generate'
987 ],
988 'options' => [
989 'required' => false,
990 'type' => 'object',
991 'description' => 'Additional options for breadcrumb generation'
992 ]
993 ];
994 }
995
996 /**
997 * Get arguments for robots.txt endpoints
998 *
999 * @since 1.0.0
1000 *
1001 * @return array Arguments array
1002 */
1003 private function get_robots_txt_args(): array {
1004 return [
1005 'custom_rules' => [
1006 'required' => false,
1007 'type' => 'array',
1008 'items' => [
1009 'type' => 'object'
1010 ],
1011 'description' => 'Custom robots.txt rules'
1012 ],
1013 'enable_management' => [
1014 'required' => false,
1015 'type' => 'boolean',
1016 'default' => true,
1017 'description' => 'Enable automatic robots.txt management'
1018 ]
1019 ];
1020 }
1021
1022 /**
1023 * Get arguments for optimization endpoint
1024 *
1025 * @since 1.0.0
1026 *
1027 * @return array Arguments array
1028 */
1029 private function get_optimization_args(): array {
1030 return [
1031 'identity_data' => [
1032 'required' => true,
1033 'type' => 'object',
1034 'description' => 'Site identity data to optimize'
1035 ]
1036 ];
1037 }
1038
1039 /**
1040 * Get arguments for AI optimization endpoint
1041 *
1042 * @since 1.0.0
1043 *
1044 * @return array Arguments array
1045 */
1046 private function get_ai_optimization_args(): array {
1047 return [
1048 'site_data' => [
1049 'required' => true,
1050 'type' => 'object',
1051 'description' => 'Site identity data to optimize with AI',
1052 'properties' => [
1053 'site_name' => [
1054 'type' => 'string',
1055 'description' => 'Site name to optimize'
1056 ],
1057 'site_description' => [
1058 'type' => 'string',
1059 'description' => 'Site description to optimize'
1060 ],
1061 'tagline' => [
1062 'type' => 'string',
1063 'description' => 'Site tagline to optimize'
1064 ]
1065 ]
1066 ],
1067 'business_type' => [
1068 'required' => false,
1069 'type' => 'string',
1070 'default' => 'website',
1071 'sanitize_callback' => 'sanitize_text_field',
1072 'description' => 'Type of business for context'
1073 ],
1074 'target_audience' => [
1075 'required' => false,
1076 'type' => 'string',
1077 'default' => 'general',
1078 'sanitize_callback' => 'sanitize_text_field',
1079 'description' => 'Target audience for optimization'
1080 ],
1081 'tone' => [
1082 'required' => false,
1083 'type' => 'string',
1084 'default' => 'professional',
1085 'sanitize_callback' => 'sanitize_text_field',
1086 'description' => 'Desired tone for optimization'
1087 ]
1088 ];
1089 }
1090
1091 /**
1092 * Get arguments for hero AI optimization endpoint
1093 *
1094 * @since 1.0.0
1095 *
1096 * @return array Arguments array
1097 */
1098 private function get_hero_optimization_args(): array {
1099 return [
1100 'hero_data' => [
1101 'required' => true,
1102 'type' => 'object',
1103 'description' => 'Hero content data to optimize with AI',
1104 'properties' => [
1105 'hero_title' => [
1106 'type' => 'string',
1107 'description' => 'Hero section title'
1108 ],
1109 'hero_subtitle' => [
1110 'type' => 'string',
1111 'description' => 'Hero section subtitle'
1112 ],
1113 'hero_cta_text' => [
1114 'type' => 'string',
1115 'description' => 'Call-to-action button text'
1116 ],
1117 'hero_cta_url' => [
1118 'type' => 'string',
1119 'description' => 'Call-to-action button URL'
1120 ]
1121 ]
1122 ],
1123 'context' => [
1124 'required' => false,
1125 'type' => 'object',
1126 'description' => 'Additional context for optimization',
1127 'properties' => [
1128 'site_name' => [
1129 'type' => 'string',
1130 'description' => 'Site name for context'
1131 ],
1132 'site_url' => [
1133 'type' => 'string',
1134 'description' => 'Site URL for context'
1135 ],
1136 'business_type' => [
1137 'type' => 'string',
1138 'description' => 'Business type for context'
1139 ],
1140 'site_description' => [
1141 'type' => 'string',
1142 'description' => 'Site description for context'
1143 ]
1144 ]
1145 ],
1146 'business_type' => [
1147 'required' => false,
1148 'type' => 'string',
1149 'default' => 'website',
1150 'sanitize_callback' => 'sanitize_text_field',
1151 'description' => 'Type of business for context'
1152 ],
1153 'target_audience' => [
1154 'required' => false,
1155 'type' => 'string',
1156 'default' => 'general',
1157 'sanitize_callback' => 'sanitize_text_field',
1158 'description' => 'Target audience for optimization'
1159 ],
1160 'tone' => [
1161 'required' => false,
1162 'type' => 'string',
1163 'default' => 'professional',
1164 'sanitize_callback' => 'sanitize_text_field',
1165 'description' => 'Desired tone for optimization'
1166 ]
1167 ];
1168 }
1169
1170 /**
1171 * Get arguments for validation endpoint
1172 *
1173 * @since 1.0.0
1174 *
1175 * @return array Arguments array
1176 */
1177 private function get_validation_args(): array {
1178 return [
1179 'settings' => [
1180 'required' => true,
1181 'type' => 'object',
1182 'description' => 'Settings to validate'
1183 ]
1184 ];
1185 }
1186
1187 /**
1188 * Check rate limit for robots.txt operations
1189 *
1190 * @since 1.0.0
1191 * @return bool True if within rate limit
1192 */
1193 private function check_robots_rate_limit(): bool {
1194 $user_id = get_current_user_id();
1195 $rate_key = "thinkrank_robots_rate_{$user_id}";
1196
1197 $requests = get_transient($rate_key) ?: 0;
1198
1199 if ($requests >= 3) { // Max 3 requests per 5 minutes
1200 return false;
1201 }
1202
1203 set_transient($rate_key, $requests + 1, 5 * MINUTE_IN_SECONDS);
1204 return true;
1205 }
1206 }
1207