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-instant-indexing-endpoint.php

class-instant-indexing-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-instant-indexing-endpoint.php

470 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Instant Indexing API Endpoints Class
5 *
6 * REST API endpoints for Instant Indexing management including
7 * IndexNow settings, post type selection, and API key management.
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 ThinkRank\Core\Settings;
19 use WP_REST_Controller;
20 use WP_REST_Request;
21 use WP_REST_Response;
22 use WP_Error;
23
24 /**
25 * Instant Indexing API Endpoints Class
26 *
27 * Provides REST API endpoints for Instant Indexing operations.
28 *
29 * @since 1.0.0
30 */
31 class Instant_Indexing_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 = 'instant-indexing';
48
49 /**
50 * Settings option name
51 *
52 * @since 1.0.0
53 * @var string
54 */
55 private $option_name = 'thinkrank_instant_indexing_settings';
56
57 /**
58 * Register API routes
59 *
60 * @since 1.0.0
61 */
62 public function register_routes(): void {
63 // Get settings
64 register_rest_route(
65 $this->namespace,
66 '/' . $this->rest_base . '/settings',
67 [
68 [
69 'methods' => 'GET',
70 'callback' => [$this, 'get_settings'],
71 'permission_callback' => [$this, 'check_read_permissions']
72 ],
73 [
74 'methods' => 'POST',
75 'callback' => [$this, 'update_settings'],
76 'permission_callback' => [$this, 'check_manage_permissions'],
77 'args' => $this->get_settings_args()
78 ]
79 ]
80 );
81
82 // Get viewable post types
83 register_rest_route(
84 $this->namespace,
85 '/' . $this->rest_base . '/post-types',
86 [
87 [
88 'methods' => 'GET',
89 'callback' => [$this, 'get_post_types'],
90 'permission_callback' => [$this, 'check_read_permissions']
91 ]
92 ]
93 );
94
95 // Regenerate API Key
96 register_rest_route(
97 $this->namespace,
98 '/' . $this->rest_base . '/regenerate-key',
99 [
100 [
101 'methods' => 'POST',
102 'callback' => [$this, 'regenerate_api_key'],
103 'permission_callback' => [$this, 'check_manage_permissions']
104 ]
105 ]
106 );
107 // Submit URLs manually
108 register_rest_route(
109 $this->namespace,
110 '/' . $this->rest_base . '/submit',
111 [
112 [
113 'methods' => 'POST',
114 'callback' => [$this, 'submit_urls_to_api'],
115 'permission_callback' => [$this, 'check_manage_permissions'],
116 'args' => [
117 'urls' => [
118 'required' => true,
119 'type' => 'string', // Textarea content
120 'description' => 'List of URLs to submit'
121 ]
122 ]
123 ]
124 ]
125 );
126
127 // Get submission history
128 register_rest_route(
129 $this->namespace,
130 '/' . $this->rest_base . '/history',
131 [
132 [
133 'methods' => 'GET',
134 'callback' => [$this, 'get_submission_history'],
135 'permission_callback' => [$this, 'check_read_permissions'],
136 'args' => [
137 'limit' => [
138 'required' => false,
139 'type' => 'integer',
140 'default' => -1
141 ]
142 ]
143 ],
144 [
145 'methods' => 'DELETE',
146 'callback' => [$this, 'clear_submission_history'],
147 'permission_callback' => [$this, 'check_manage_permissions']
148 ]
149 ]
150 );
151 }
152
153 /**
154 * Get settings
155 *
156 * @since 1.0.0
157 *
158 * @param WP_REST_Request $request Request object
159 * @return WP_REST_Response Response object
160 */
161 public function get_settings(WP_REST_Request $request): WP_REST_Response {
162 $settings = get_option($this->option_name, []);
163
164 $defaults = [
165 'enabled' => false,
166 'auto_submit_post_types' => ['post', 'page'],
167 'api_key' => ''
168 ];
169
170 $settings = wp_parse_args($settings, $defaults);
171
172 // Ensure api_key is always present
173 if (empty($settings['api_key'])) {
174 $settings['api_key'] = $this->generate_api_key();
175 $this->manage_key_file($settings['api_key']);
176 update_option($this->option_name, $settings);
177 } else {
178 // Verify file exists for existing key, create if missing
179 $file_path = ABSPATH . $settings['api_key'] . '.txt';
180 if (!file_exists($file_path)) {
181 $this->manage_key_file($settings['api_key']);
182 }
183 }
184
185 return new WP_REST_Response([
186 'success' => true,
187 'data' => $settings
188 ], 200);
189 }
190
191 /**
192 * Update settings
193 *
194 * @since 1.0.0
195 *
196 * @param WP_REST_Request $request Request object
197 * @return WP_REST_Response|WP_Error Response object or error
198 */
199 public function update_settings(WP_REST_Request $request) {
200 $params = $request->get_json_params();
201
202 if (empty($params)) {
203 $params = $request->get_params(); // Fallback if content-type is not JSON
204 }
205
206 // Sanitize Post Types
207 $post_types = isset($params['auto_submit_post_types']) ? (array) $params['auto_submit_post_types'] : [];
208 $sanitized_post_types = array_map('sanitize_text_field', $post_types);
209
210 // We generally don't let user update API Key directly via update_settings,
211 // they should use regenerate, but if we need to support manual entry:
212 $current_settings = get_option($this->option_name, []);
213 $new_settings = array_merge($current_settings, [
214 'auto_submit_post_types' => $sanitized_post_types
215 ]);
216
217 // Save enabled state
218 if (isset($params['enabled'])) {
219 $new_settings['enabled'] = rest_sanitize_boolean($params['enabled']);
220 }
221
222 // If API key is provided and different (rare case), sanitize it
223 if (isset($params['api_key'])) {
224 $new_settings['api_key'] = sanitize_text_field($params['api_key']);
225 }
226
227 update_option($this->option_name, $new_settings);
228
229 return new WP_REST_Response([
230 'success' => true,
231 'message' => __('Settings updated successfully', 'thinkrank'),
232 'data' => $new_settings
233 ], 200);
234 }
235
236 /**
237 * Get viewable post types
238 *
239 * Uses custom args as per requirements.
240 *
241 * @since 1.0.0
242 *
243 * @param WP_REST_Request $request Request object
244 * @return WP_REST_Response Response object
245 */
246 public function get_post_types(WP_REST_Request $request): WP_REST_Response {
247 $args = [
248 'public' => true,
249 ];
250
251 $post_types = get_post_types($args, "objects");
252 $post_types = array_filter($post_types, 'is_post_type_viewable');
253
254 $data = [];
255 foreach ($post_types as $post_type) {
256 $data[] = [
257 'slug' => $post_type->name,
258 'name' => $post_type->label,
259 'singular_name' => $post_type->labels->singular_name
260 ];
261 }
262
263 return new WP_REST_Response([
264 'success' => true,
265 'data' => $data
266 ], 200);
267 }
268
269 /**
270 * Regenerate API Key
271 *
272 * @since 1.0.0
273 *
274 * @param WP_REST_Request $request Request object
275 * @return WP_REST_Response Response object
276 */
277 public function regenerate_api_key(WP_REST_Request $request): WP_REST_Response {
278 $settings = get_option($this->option_name, []);
279 $old_key = $settings['api_key'] ?? null;
280
281 $new_key = $this->generate_api_key();
282
283 if (!is_array($settings)) {
284 $settings = [];
285 }
286
287 $settings['api_key'] = $new_key;
288 update_option($this->option_name, $settings);
289
290 // Update key files (create new, delete old)
291 $this->manage_key_file($new_key, $old_key);
292
293 return new WP_REST_Response([
294 'success' => true,
295 'key' => $new_key,
296 'message' => __('API Key regenerated successfully', 'thinkrank')
297 ], 200);
298 }
299
300 /**
301 * Manage API Key File (Create new, delete old)
302 *
303 * @param string $new_key New API Key
304 * @param string|null $old_key Old API Key to delete
305 * @return void
306 */
307 private function manage_key_file(string $new_key, ?string $old_key = null): void {
308 // Create new file
309 if (!empty($new_key)) {
310 $file_path = ABSPATH . $new_key . '.txt';
311 if (wp_is_writable(ABSPATH)) {
312 file_put_contents($file_path, $new_key);
313 }
314 }
315
316 // Delete old file
317 if (!empty($old_key) && $old_key !== $new_key) {
318 $old_file_path = ABSPATH . $old_key . '.txt';
319 if (file_exists($old_file_path) && wp_is_writable($old_file_path)) {
320 unlink($old_file_path);
321 }
322 }
323 }
324
325 /**
326 * Generate a random API key (32 chars hex)
327 *
328 * @return string
329 */
330 private function generate_api_key(): string {
331 try {
332 return bin2hex(random_bytes(16));
333 } catch (\Exception $e) {
334 // Fallback if random_bytes fails
335 return md5(uniqid((string) wp_rand(), true));
336 }
337 }
338
339 /**
340 * Submit URLs manually
341 *
342 * @since 1.1.0
343 *
344 * @param WP_REST_Request $request Request object
345 * @return WP_REST_Response Response object
346 */
347 public function submit_urls_to_api(WP_REST_Request $request): WP_REST_Response {
348 $urls_param = $request->get_param('urls');
349 $urls = array_filter(array_map('trim', explode("\n", $urls_param)));
350
351 if (empty($urls)) {
352 return new WP_REST_Response([
353 'success' => false,
354 'message' => __('No valid URLs provided', 'thinkrank')
355 ], 400);
356 }
357
358 // Limit to 100 for manual submission safety
359 if (count($urls) > 100) {
360 $urls = array_slice($urls, 0, 100);
361 }
362
363 $manager = new \ThinkRank\SEO\Instant_Indexing_Manager();
364 $result = $manager->submit_urls($urls);
365
366 return new WP_REST_Response([
367 'success' => $result['success'],
368 'message' => $result['message'],
369 'count' => count($urls)
370 ], 200);
371 }
372
373 /**
374 * Get submission history
375 *
376 * @since 1.1.0
377 *
378 * @param WP_REST_Request $request Request object
379 * @return WP_REST_Response Response object
380 */
381 public function get_submission_history(WP_REST_Request $request): WP_REST_Response {
382 $limit = $request->get_param('limit') ?: -1;
383
384 $manager = new \ThinkRank\SEO\Instant_Indexing_Manager();
385 $history = $manager->get_history((int) $limit);
386
387 return new WP_REST_Response([
388 'success' => true,
389 'data' => $history
390 ], 200);
391 }
392
393 /**
394 * Clear submission history
395 *
396 * @since 1.1.0
397 *
398 * @param WP_REST_Request $request Request object
399 * @return WP_REST_Response Response object
400 */
401 public function clear_submission_history(WP_REST_Request $request): WP_REST_Response {
402 $manager = new \ThinkRank\SEO\Instant_Indexing_Manager();
403 $result = $manager->clear_history();
404
405 if ($result) {
406 return new WP_REST_Response([
407 'success' => true,
408 'message' => __('History cleared successfully', 'thinkrank')
409 ], 200);
410 }
411
412 return new WP_REST_Response([
413 'success' => false,
414 'message' => __('Failed to clear history', 'thinkrank')
415 ], 500);
416 }
417
418 /**
419 * Check read permissions
420 *
421 * @since 1.0.0
422 *
423 * @return bool Permission status
424 */
425 public function check_read_permissions(): bool {
426 return current_user_can('edit_posts');
427 }
428
429 /**
430 * Check manage permissions
431 *
432 * @since 1.0.0
433 *
434 * @return bool Permission status
435 */
436 public function check_manage_permissions(): bool {
437 return current_user_can('manage_options');
438 }
439
440 /**
441 * Get arguments for settings endpoints
442 *
443 * @since 1.0.0
444 *
445 * @return array Arguments array
446 */
447 private function get_settings_args(): array {
448 return [
449 'auto_submit_post_types' => [
450 'required' => false,
451 'type' => 'array',
452 'items' => [
453 'type' => 'string'
454 ],
455 'description' => 'List of post types to auto-submit'
456 ],
457 'api_key' => [
458 'required' => false,
459 'type' => 'string',
460 'description' => 'IndexNow API Key'
461 ],
462 'enabled' => [
463 'required' => false,
464 'type' => 'boolean',
465 'description' => 'Enable or disable Instant Indexing'
466 ]
467 ];
468 }
469 }
470