PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.10.0 2.9.0 2.8.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 All 51 releases
← All changes | includes/api/class-instant-indexing-endpoint.php +187 -10 1.26.0 → 2.10.0 View file →
@@ -15,13 +15,19 @@
15 15
16 16 namespace ThinkRank\API;
17 17
18 18 use ThinkRank\Core\Settings;
19 +use ThinkRank\SEO\Instant_Indexing_Reconciler;
19 20 use WP_REST_Controller;
20 21 use WP_REST_Request;
21 22 use WP_REST_Response;
22 23 use WP_Error;
23 24
25 +// Prevent direct access
26 +if (!defined('ABSPATH')) {
27 + exit;
28 +}
29 +
24 30 /**
25 31 * Instant Indexing API Endpoints Class
26 32 *
27 33 * Provides REST API endpoints for Instant Indexing operations.
@@ -54,8 +60,16 @@
54 60 */
55 61 private $option_name = 'thinkrank_instant_indexing_settings';
56 62
57 63 /**
64 + * Reconciler instance
65 + *
66 + * @since 1.31.0
67 + * @var Instant_Indexing_Reconciler|null
68 + */
69 + private ?Instant_Indexing_Reconciler $reconciler = null;
70 +
71 + /**
58 72 * Register API routes
59 73 *
60 74 * @since 1.0.0
61 75 */
@@ -123,8 +137,21 @@
123 137 ]
124 138 ]
125 139 );
126 140
141 + // Verify the advertised key file is actually reachable (see #247).
142 + register_rest_route(
143 + $this->namespace,
144 + '/' . $this->rest_base . '/verify-key',
145 + [
146 + [
147 + 'methods' => 'GET',
148 + 'callback' => [$this, 'verify_key'],
149 + 'permission_callback' => [$this, 'check_read_permissions']
150 + ]
151 + ]
152 + );
153 +
127 154 // Get submission history
128 155 register_rest_route(
129 156 $this->namespace,
130 157 '/' . $this->rest_base . '/history',
@@ -137,9 +164,25 @@
137 164 'limit' => [
138 165 'required' => false,
139 166 'type' => 'integer',
140 167 'default' => -1
141 - ]
168 + ],
169 + // Both are read by get_submission_history() and neither
170 + // was registered, so they arrived uncoerced and
171 + // unbounded (#394).
172 + 'page' => [
173 + 'required' => false,
174 + 'type' => 'integer',
175 + 'default' => 1,
176 + 'minimum' => 1,
177 + ],
178 + 'per_page' => [
179 + 'required' => false,
180 + 'type' => 'integer',
181 + 'default' => 20,
182 + 'minimum' => 1,
183 + 'maximum' => 100,
184 + ],
142 185 ]
143 186 ],
144 187 [
145 188 'methods' => 'DELETE',
@@ -147,11 +190,115 @@
147 190 'permission_callback' => [$this, 'check_manage_permissions']
148 191 ]
149 192 ]
150 193 );
194 +
195 + // Coverage report: which published URLs IndexNow actually knows about.
196 + register_rest_route(
197 + $this->namespace,
198 + '/' . $this->rest_base . '/coverage',
199 + [
200 + [
201 + 'methods' => 'GET',
202 + 'callback' => [$this, 'get_coverage_report'],
203 + 'permission_callback' => [$this, 'check_read_permissions'],
204 + 'args' => [
205 + 'limit' => [
206 + 'required' => false,
207 + 'type' => 'integer',
208 + 'default' => Instant_Indexing_Reconciler::REPORT_LIMIT,
209 + 'minimum' => 1,
210 + 'maximum' => 2000
211 + ],
212 + 'offset' => [
213 + 'required' => false,
214 + 'type' => 'integer',
215 + 'default' => 0,
216 + 'minimum' => 0
217 + ]
218 + ]
219 + ]
220 + ]
221 + );
222 +
223 + // Run a reconciliation pass now instead of waiting for the daily cron.
224 + register_rest_route(
225 + $this->namespace,
226 + '/' . $this->rest_base . '/reconcile',
227 + [
228 + [
229 + 'methods' => 'POST',
230 + // Resubmits URLs to a third party, so this needs the manage
231 + // capability rather than the read one.
232 + 'callback' => [$this, 'run_reconciliation'],
233 + 'permission_callback' => [$this, 'check_manage_permissions'],
234 + 'args' => [
235 + 'dry_run' => [
236 + 'required' => false,
237 + 'type' => 'boolean',
238 + 'default' => false
239 + ]
240 + ]
241 + ]
242 + ]
243 + );
151 244 }
152 245
153 246 /**
247 + * Get the IndexNow coverage report.
248 + *
249 + * @since 1.31.0
250 + *
251 + * @param WP_REST_Request $request Request object
252 + * @return WP_REST_Response Response object
253 + */
254 + public function get_coverage_report(WP_REST_Request $request): WP_REST_Response {
255 + $report = $this->get_reconciler()->build_report(
256 + (int) $request->get_param('limit'),
257 + (int) $request->get_param('offset')
258 + );
259 +
260 + return new WP_REST_Response([
261 + 'success' => true,
262 + 'data' => $report,
263 + ], 200);
264 + }
265 +
266 + /**
267 + * Run a reconciliation pass on demand.
268 + *
269 + * @since 1.31.0
270 + *
271 + * @param WP_REST_Request $request Request object
272 + * @return WP_REST_Response Response object
273 + */
274 + public function run_reconciliation(WP_REST_Request $request): WP_REST_Response {
275 + $summary = $this->get_reconciler()->reconcile((bool) $request->get_param('dry_run'));
276 +
277 + return new WP_REST_Response([
278 + 'success' => true,
279 + 'data' => $summary,
280 + 'message' => $summary['ran']
281 + ? sprintf('Reconciliation complete: %d URLs examined, %d resubmitted.', $summary['examined'], $summary['retried'])
282 + : $summary['reason'],
283 + ], 200);
284 + }
285 +
286 + /**
287 + * Reconciler instance, built on first use.
288 + *
289 + * @since 1.31.0
290 + * @return Instant_Indexing_Reconciler
291 + */
292 + private function get_reconciler(): Instant_Indexing_Reconciler {
293 + if (null === $this->reconciler) {
294 + $this->reconciler = new Instant_Indexing_Reconciler();
295 + }
296 +
297 + return $this->reconciler;
298 + }
299 +
300 + /**
154 301 * Get settings
155 302 *
156 303 * @since 1.0.0
157 304 *
@@ -202,18 +349,26 @@
202 349 if (empty($params)) {
203 350 $params = $request->get_params(); // Fallback if content-type is not JSON
204 351 }
205 352
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);
353 + $current_settings = get_option($this->option_name, []);
354 + if (!is_array($current_settings)) {
355 + $current_settings = [];
356 + }
357 + $new_settings = $current_settings;
209 358
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 - ]);
359 + // Only write the post types when the caller actually sent them. Writing
360 + // unconditionally meant a payload of {"enabled": true} cleared the list,
361 + // so the feature came on with nothing to submit — and diverged from the
362 + // MCP ability, which writes this same option with an array_key_exists()
363 + // merge. An explicit empty array still clears, since isset() is true
364 + // for one (#562).
365 + if (isset($params['auto_submit_post_types'])) {
366 + $new_settings['auto_submit_post_types'] = array_values(array_map(
367 + 'sanitize_key',
368 + (array) $params['auto_submit_post_types']
369 + ));
370 + }
216 371
217 372 // Save enabled state
218 373 if (isset($params['enabled'])) {
219 374 $new_settings['enabled'] = rest_sanitize_boolean($params['enabled']);
@@ -395,8 +550,30 @@
395 550 return new WP_REST_Response([
396 551 'success' => $result['success'],
397 552 'message' => $result['message'],
398 553 'count' => (int) ($result['submitted_count'] ?? 0)
554 + ], 200);
555 + }
556 +
557 + /**
558 + * Verify the advertised IndexNow key file is reachable and returns the key.
559 + *
560 + * Runs a one-shot loopback fetch of keyLocation so an unreachable-key
561 + * configuration (read-only root + Plain permalinks, a CDN edge rule, etc.)
562 + * surfaces on the settings screen instead of as a silent 403 at first
563 + * submission (see #247).
564 + *
565 + * @since 1.28.0
566 + *
567 + * @param WP_REST_Request $request Request object
568 + * @return WP_REST_Response Response object
569 + */
570 + public function verify_key(WP_REST_Request $request): WP_REST_Response {
571 + $manager = new \ThinkRank\SEO\Instant_Indexing_Manager();
572 +
573 + return new WP_REST_Response([
574 + 'success' => true,
575 + 'data' => $manager->verify_key_reachable(),
399 576 ], 200);
400 577 }
401 578
402 579 /**