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

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

490 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Global SEO API Endpoints Class
4 *
5 * REST API endpoints for managing global SEO settings across different WordPress post types.
6 * Provides functionality to save and retrieve SEO settings including title formats,
7 * meta descriptions, schema types, and article types for all registered post types.
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 use WP_REST_Controller;
19 use WP_REST_Request;
20 use WP_REST_Response;
21 use WP_Error;
22
23 /**
24 * Global SEO API Endpoints Class
25 *
26 * Provides REST API endpoints for managing global SEO settings for different post types
27 * including title formats, meta descriptions, schema types, and article types.
28 *
29 * @since 1.0.0
30 */
31 class Global_SEO_Endpoint extends WP_REST_Controller {
32
33 /**
34 * API namespace
35 *
36 * @since 1.0.0
37 * @var string
38 */
39 protected $namespace = 'thinkrank/v1';
40
41 /**
42 * API resource base
43 *
44 * @since 1.0.0
45 * @var string
46 */
47 protected $rest_base = 'global-seo';
48
49 /**
50 * WordPress option name for storing global SEO settings
51 *
52 * @since 1.0.0
53 * @var string
54 */
55 private const OPTION_NAME = 'thinkrank_global_seo_settings';
56
57 /**
58 * Default settings structure for post types
59 *
60 * @since 1.0.0
61 * @var array
62 */
63 private const DEFAULT_SETTINGS = [
64 'title' => '%title% %sep% %sitename%',
65 'description' => '%excerpt%',
66 'schema_type' => 'WebPage',
67 'article_type' => '',
68 'media_type' => ''
69 ];
70
71 /**
72 * Register API routes
73 *
74 * @since 1.0.0
75 */
76 public function register_routes(): void {
77 // Get/Save global SEO settings
78 register_rest_route(
79 $this->namespace,
80 '/' . $this->rest_base . '/settings',
81 [
82 [
83 'methods' => 'GET',
84 'callback' => [$this, 'get_settings'],
85 'permission_callback' => [$this, 'check_read_permissions'],
86 'args' => $this->get_settings_query_args()
87 ],
88 [
89 'methods' => 'POST',
90 'callback' => [$this, 'save_settings'],
91 'permission_callback' => [$this, 'check_manage_permissions'],
92 'args' => $this->get_save_settings_args()
93 ]
94 ]
95 );
96
97 // Get all global SEO settings (all post types)
98 register_rest_route(
99 $this->namespace,
100 '/' . $this->rest_base . '/settings/all',
101 [
102 [
103 'methods' => 'GET',
104 'callback' => [$this, 'get_all_settings'],
105 'permission_callback' => [$this, 'check_read_permissions']
106 ]
107 ]
108 );
109
110 // Reset settings for a specific post type
111 register_rest_route(
112 $this->namespace,
113 '/' . $this->rest_base . '/settings/reset',
114 [
115 [
116 'methods' => 'POST',
117 'callback' => [$this, 'reset_settings'],
118 'permission_callback' => [$this, 'check_manage_permissions'],
119 'args' => [
120 'post_type' => [
121 'required' => true,
122 'type' => 'string',
123 'description' => 'Post type to reset settings for',
124 'sanitize_callback' => 'sanitize_key'
125 ]
126 ]
127 ]
128 ]
129 );
130 }
131
132 /**
133 * Get global SEO settings for a specific post type
134 *
135 * @since 1.0.0
136 *
137 * @param WP_REST_Request $request Request object
138 * @return WP_REST_Response|WP_Error Response object or error
139 */
140 public function get_settings(WP_REST_Request $request) {
141 $post_type = $request->get_param('post_type');
142
143 // Validate post type
144 $validation = $this->validate_post_type($post_type);
145 if (is_wp_error($validation)) {
146 return $validation;
147 }
148
149 // Get all settings
150 $all_settings = get_option(self::OPTION_NAME, []);
151
152 // Get settings for specific post type or return defaults
153 $settings = $all_settings[$post_type] ?? $this->get_default_settings($post_type);
154
155 return new WP_REST_Response([
156 'success' => true,
157 'data' => $settings,
158 'post_type' => $post_type,
159 'message' => sprintf('Settings retrieved successfully for post type: %s', $post_type)
160 ], 200);
161 }
162
163 /**
164 * Save global SEO settings for a specific post type
165 *
166 * @since 1.0.0
167 *
168 * @param WP_REST_Request $request Request object
169 * @return WP_REST_Response|WP_Error Response object or error
170 */
171 public function save_settings(WP_REST_Request $request) {
172 $post_type = $request->get_param('post_type');
173 $settings = $request->get_param('settings');
174
175 // Validate post type
176 $validation = $this->validate_post_type($post_type);
177 if (is_wp_error($validation)) {
178 return $validation;
179 }
180
181 // Validate settings structure
182 if (empty($settings) || !is_array($settings)) {
183 return new WP_Error(
184 'invalid_settings',
185 'Settings must be provided as an array',
186 ['status' => 400]
187 );
188 }
189
190 // Sanitize settings
191 $sanitized_settings = $this->sanitize_settings($settings);
192
193 // Get all existing settings
194 $all_settings = get_option(self::OPTION_NAME, []);
195
196 // Update settings for this post type
197 $all_settings[$post_type] = $sanitized_settings;
198
199 // Save to database
200 $updated = update_option(self::OPTION_NAME, $all_settings);
201
202 if ($updated || $all_settings[$post_type] === $sanitized_settings) {
203 return new WP_REST_Response([
204 'success' => true,
205 'data' => $sanitized_settings,
206 'post_type' => $post_type,
207 'message' => sprintf('Settings saved successfully for post type: %s', $post_type)
208 ], 200);
209 }
210
211 return new WP_Error(
212 'save_failed',
213 'Failed to save settings',
214 ['status' => 500]
215 );
216 }
217
218 /**
219 * Get all global SEO settings for all post types
220 *
221 * @since 1.0.0
222 *
223 * @param WP_REST_Request $request Request object
224 * @return WP_REST_Response Response object
225 */
226 public function get_all_settings(WP_REST_Request $request): WP_REST_Response {
227 $all_settings = get_option(self::OPTION_NAME, []);
228
229 return new WP_REST_Response([
230 'success' => true,
231 'data' => $all_settings,
232 'count' => count($all_settings),
233 'message' => 'All global SEO settings retrieved successfully'
234 ], 200);
235 }
236
237 /**
238 * Reset settings for a specific post type to defaults
239 *
240 * @since 1.0.0
241 *
242 * @param WP_REST_Request $request Request object
243 * @return WP_REST_Response|WP_Error Response object or error
244 */
245 public function reset_settings(WP_REST_Request $request) {
246 $post_type = $request->get_param('post_type');
247
248 // Validate post type
249 $validation = $this->validate_post_type($post_type);
250 if (is_wp_error($validation)) {
251 return $validation;
252 }
253
254 // Get all settings
255 $all_settings = get_option(self::OPTION_NAME, []);
256
257 // Remove settings for this post type (will fall back to defaults)
258 unset($all_settings[$post_type]);
259
260 // Save updated settings
261 update_option(self::OPTION_NAME, $all_settings);
262
263 // Get default settings
264 $default_settings = $this->get_default_settings($post_type);
265
266 return new WP_REST_Response([
267 'success' => true,
268 'data' => $default_settings,
269 'post_type' => $post_type,
270 'message' => sprintf('Settings reset to defaults for post type: %s', $post_type)
271 ], 200);
272 }
273
274
275
276
277 /**
278 * Validate post type
279 *
280 * @since 1.0.0
281 *
282 * @param string $post_type Post type to validate
283 * @return true|WP_Error True if valid, WP_Error otherwise
284 */
285 private function validate_post_type(string $post_type) {
286 if (empty($post_type)) {
287 return new WP_Error(
288 'missing_post_type',
289 'Post type parameter is required',
290 ['status' => 400]
291 );
292 }
293
294 // Check if post type exists
295 if (!post_type_exists($post_type)) {
296 return new WP_Error(
297 'invalid_post_type',
298 sprintf('Post type "%s" does not exist', $post_type),
299 ['status' => 400]
300 );
301 }
302
303 // Check if post type is public
304 $post_type_object = get_post_type_object($post_type);
305 if (!$post_type_object || !$post_type_object->public) {
306 return new WP_Error(
307 'non_public_post_type',
308 sprintf('Post type "%s" is not public', $post_type),
309 ['status' => 400]
310 );
311 }
312
313 return true;
314 }
315
316 /**
317 * Get default settings for a post type
318 *
319 * @since 1.0.0
320 *
321 * @param string $post_type Post type
322 * @return array Default settings
323 */
324 private function get_default_settings(string $post_type): array {
325 $defaults = self::DEFAULT_SETTINGS;
326
327 // Customize defaults based on post type
328 switch ($post_type) {
329 case 'post':
330 $defaults['schema_type'] = 'Article';
331 $defaults['article_type'] = 'BlogPosting';
332 $defaults['media_type'] = '';
333 break;
334
335 case 'page':
336 $defaults['schema_type'] = 'WebPage';
337 $defaults['article_type'] = '';
338 $defaults['media_type'] = '';
339 break;
340
341 case 'attachment':
342 $defaults['schema_type'] = 'Media';
343 $defaults['article_type'] = '';
344 $defaults['media_type'] = 'ImageObject';
345 $defaults['title'] = '%title% %sep% %sitename%';
346 $defaults['description'] = '%caption%';
347 break;
348
349 case 'product':
350 $defaults['schema_type'] = 'Product';
351 $defaults['article_type'] = '';
352 $defaults['media_type'] = '';
353 break;
354
355 default:
356 // For custom post types, use generic defaults
357 $defaults['schema_type'] = 'WebPage';
358 $defaults['article_type'] = '';
359 $defaults['media_type'] = '';
360 break;
361 }
362
363 return $defaults;
364 }
365
366 /**
367 * Sanitize settings array
368 *
369 * @since 1.0.0
370 *
371 * @param array $settings Settings to sanitize
372 * @return array Sanitized settings
373 */
374 private function sanitize_settings(array $settings): array {
375 $sanitized = [];
376
377 // Sanitize title
378 if (isset($settings['title'])) {
379 $sanitized['title'] = sanitize_text_field($settings['title']);
380 }
381
382 // Sanitize description
383 if (isset($settings['description'])) {
384 $sanitized['description'] = sanitize_text_field($settings['description']);
385 }
386
387 // Sanitize schema_type
388 if (isset($settings['schema_type'])) {
389 $sanitized['schema_type'] = sanitize_text_field($settings['schema_type']);
390 }
391
392 // Sanitize article_type
393 if (isset($settings['article_type'])) {
394 $sanitized['article_type'] = sanitize_text_field($settings['article_type']);
395 }
396
397 // Sanitize media_type
398 if (isset($settings['media_type'])) {
399 $sanitized['media_type'] = sanitize_text_field($settings['media_type']);
400 }
401
402 return $sanitized;
403 }
404
405 /**
406 * Get query arguments for GET settings endpoint
407 *
408 * @since 1.0.0
409 *
410 * @return array Arguments array
411 */
412 private function get_settings_query_args(): array {
413 return [
414 'post_type' => [
415 'required' => true,
416 'type' => 'string',
417 'description' => 'Post type to retrieve settings for',
418 'sanitize_callback' => 'sanitize_key'
419 ]
420 ];
421 }
422
423 /**
424 * Get arguments for POST save settings endpoint
425 *
426 * @since 1.0.0
427 *
428 * @return array Arguments array
429 */
430 private function get_save_settings_args(): array {
431 return [
432 'post_type' => [
433 'required' => true,
434 'type' => 'string',
435 'description' => 'Post type to save settings for',
436 'sanitize_callback' => 'sanitize_key'
437 ],
438 'settings' => [
439 'required' => true,
440 'type' => 'object',
441 'description' => 'Settings object containing title, description, schema_type, article_type, and media_type',
442 'properties' => [
443 'title' => [
444 'type' => 'string',
445 'description' => 'Title format with variables like %title%, %sitename%, %sep%'
446 ],
447 'description' => [
448 'type' => 'string',
449 'description' => 'Description format with variables like %excerpt%'
450 ],
451 'schema_type' => [
452 'type' => 'string',
453 'description' => 'Schema.org type (e.g., Article, WebPage, Media, Product)'
454 ],
455 'article_type' => [
456 'type' => 'string',
457 'description' => 'Article type (e.g., BlogPosting, NewsArticle) - used when schema_type is Article'
458 ],
459 'media_type' => [
460 'type' => 'string',
461 'description' => 'Media type (e.g., ImageObject, VideoObject) - used when schema_type is Media'
462 ]
463 ]
464 ]
465 ];
466 }
467
468 /**
469 * Check read permissions
470 *
471 * @since 1.0.0
472 *
473 * @return bool True if user has read permissions
474 */
475 public function check_read_permissions(): bool {
476 return current_user_can('edit_posts');
477 }
478
479 /**
480 * Check manage permissions
481 *
482 * @since 1.0.0
483 *
484 * @return bool True if user has manage permissions
485 */
486 public function check_manage_permissions(): bool {
487 return current_user_can('manage_options');
488 }
489 }
490