PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.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 1.0.2 All 50 releases
← All changes | includes/api/class-instant-indexing-endpoint.php +152 -10 1.28.0 → 2.9.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 */
@@ -150,9 +164,25 @@
150 164 'limit' => [
151 165 'required' => false,
152 166 'type' => 'integer',
153 167 'default' => -1
154 - ]
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 + ],
155 185 ]
156 186 ],
157 187 [
158 188 'methods' => 'DELETE',
@@ -160,11 +190,115 @@
160 190 'permission_callback' => [$this, 'check_manage_permissions']
161 191 ]
162 192 ]
163 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 + );
164 244 }
165 245
166 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 + /**
167 301 * Get settings
168 302 *
169 303 * @since 1.0.0
170 304 *
@@ -215,18 +349,26 @@
215 349 if (empty($params)) {
216 350 $params = $request->get_params(); // Fallback if content-type is not JSON
217 351 }
218 352
219 - // Sanitize Post Types
220 - $post_types = isset($params['auto_submit_post_types']) ? (array) $params['auto_submit_post_types'] : [];
221 - $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;
222 358
223 - // We generally don't let user update API Key directly via update_settings,
224 - // they should use regenerate, but if we need to support manual entry:
225 - $current_settings = get_option($this->option_name, []);
226 - $new_settings = array_merge($current_settings, [
227 - 'auto_submit_post_types' => $sanitized_post_types
228 - ]);
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 + }
229 371
230 372 // Save enabled state
231 373 if (isset($params['enabled'])) {
232 374 $new_settings['enabled'] = rest_sanitize_boolean($params['enabled']);