PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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
← All changes | includes/api/class-integrations-endpoint.php +24 -143 1.29.02.7.0 View file →
@@ -121,55 +121,8 @@
121 121 ]
122 122 ]
123 123 );
124 124
125 - // Verify GA4 tracking
126 - register_rest_route(
127 - $this->namespace,
128 - '/' . $this->rest_base . '/verify-ga4-tracking',
129 - [
130 - [
131 - 'methods' => 'POST',
132 - 'callback' => [$this, 'verify_ga4_tracking'],
133 - 'permission_callback' => [$this, 'check_manage_permissions'],
134 - 'args' => [
135 - 'measurement_id' => [
136 - // Optional, and an empty string is meaningful:
137 - // verify_tracking() then discovers the ID from the
138 - // live homepage. A `required` + `pattern` arg
139 - // rejected that at the REST layer before the
140 - // handler ran, which is why verification was
141 - // unreachable on sites with no stored ID (#250).
142 - 'required' => false,
143 - 'type' => 'string',
144 - 'default' => '',
145 - // No regex delimiters — WP's REST validator wraps the
146 - // pattern in its own (#...#u), so a leading/trailing
147 - // slash would require literal slashes in the value.
148 - // The empty alternative keeps discovery reachable
149 - // while still rejecting a malformed ID.
150 - 'pattern' => '^(G-[A-Z0-9]{10})?$',
151 - 'sanitize_callback' => 'sanitize_text_field',
152 - 'description' => 'GA4 Measurement ID in format G-XXXXXXXXXX. Omit or leave empty to auto-detect the ID from the site homepage.'
153 - ]
154 - ]
155 - ]
156 - ]
157 - );
158 -
159 - // Detect GA4 conflicts
160 - register_rest_route(
161 - $this->namespace,
162 - '/' . $this->rest_base . '/detect-ga4-conflicts',
163 - [
164 - [
165 - 'methods' => 'GET',
166 - 'callback' => [$this, 'detect_ga4_conflicts'],
167 - 'permission_callback' => [$this, 'check_read_permissions']
168 - ]
169 - ]
170 - );
171 -
172 125 // Get Search Console Sites
173 126 register_rest_route(
174 127 $this->namespace,
175 128 '/' . $this->rest_base . '/search-console/sites',
@@ -338,16 +291,8 @@
338 291 $settings['google_analytics_api_key'] = $this->settings->get('google_analytics_api_key');
339 292 $settings['google_search_console_api_key'] = $this->settings->get('google_search_console_api_key');
340 293 $settings['google_pagespeed_api_key'] = $this->settings->get('google_pagespeed_api_key');
341 294
342 - // Get GA4 tracking settings (let Settings class handle defaults)
343 - $settings['ga4_measurement_id'] = $this->settings->get('ga4_measurement_id');
344 - $settings['ga4_auto_inject'] = $this->settings->get('ga4_auto_inject');
345 - $settings['ga4_anonymize_ip'] = $this->settings->get('ga4_anonymize_ip');
346 - $settings['ga4_exclude_admin'] = $this->settings->get('ga4_exclude_admin');
347 - $settings['ga4_tracking_verified'] = $this->settings->get('ga4_tracking_verified');
348 - $settings['ga4_last_verification'] = $this->settings->get('ga4_last_verification');
349 -
350 295 // Get other integration settings (let Settings class handle defaults)
351 296 $settings['api_timeout'] = $this->settings->get('api_timeout');
352 297 $settings['enable_rate_limiting'] = $this->settings->get('enable_rate_limiting');
353 298 $settings['cache_duration'] = $this->settings->get('cache_duration');
@@ -403,25 +348,33 @@
403 348 $sanitized[$key_field] = sanitize_text_field($settings[$key_field]);
404 349 }
405 350 }
406 351
407 - // Sanitize numeric settings
408 - $sanitized['api_timeout'] = absint($settings['api_timeout'] ?? 30);
409 - $sanitized['cache_duration'] = absint($settings['cache_duration'] ?? 3600);
352 + // A key the payload never mentioned is left alone rather than being
353 + // reset to a hard-coded default. These fallbacks used to fire on every
354 + // save, so a partial payload — or a setting the admin has no control
355 + // for, like retry_failed_requests — silently reverted to the default a
356 + // site owner had deliberately changed in code (#297).
357 + $numeric = ['api_timeout', 'cache_duration'];
410 358
411 - // Sanitize GA4 tracking settings
412 - $sanitized['ga4_measurement_id'] = sanitize_text_field($settings['ga4_measurement_id'] ?? '');
413 - $sanitized['ga4_auto_inject'] = isset($settings['ga4_auto_inject']) ? (bool) $settings['ga4_auto_inject'] : false;
414 - $sanitized['ga4_anonymize_ip'] = isset($settings['ga4_anonymize_ip']) ? (bool) $settings['ga4_anonymize_ip'] : false;
415 - $sanitized['ga4_exclude_admin'] = isset($settings['ga4_exclude_admin']) ? (bool) $settings['ga4_exclude_admin'] : false;
416 - $sanitized['ga4_tracking_verified'] = isset($settings['ga4_tracking_verified']) ? (bool) $settings['ga4_tracking_verified'] : false;
417 - $sanitized['ga4_last_verification'] = sanitize_text_field($settings['ga4_last_verification'] ?? '');
359 + foreach ($numeric as $key) {
360 + if (array_key_exists($key, $settings)) {
361 + $sanitized[$key] = absint($settings[$key]);
362 + }
363 + }
418 364
419 - // Sanitize boolean settings
420 - $sanitized['enable_rate_limiting'] = isset($settings['enable_rate_limiting']) ? (bool) $settings['enable_rate_limiting'] : true;
421 - $sanitized['auto_test_connections'] = isset($settings['auto_test_connections']) ? (bool) $settings['auto_test_connections'] : true;
422 - $sanitized['retry_failed_requests'] = isset($settings['retry_failed_requests']) ? (bool) $settings['retry_failed_requests'] : true;
365 + $booleans = [
366 + 'enable_rate_limiting',
367 + 'auto_test_connections',
368 + 'retry_failed_requests',
369 + ];
423 370
371 + foreach ($booleans as $key) {
372 + if (array_key_exists($key, $settings)) {
373 + $sanitized[$key] = (bool) $settings[$key];
374 + }
375 + }
376 +
424 377 return $sanitized;
425 378 }
426 379
427 380 /**
@@ -802,9 +755,9 @@
802 755 * @since 1.0.0
803 756 * @param WP_REST_Request $request Request object
804 757 * @return WP_REST_Response|WP_Error Response object
805 758 */
806 - public function disconnect_google_account(WP_REST_Request $request): WP_REST_Response|WP_Error {
759 + public function disconnect_google_account(WP_REST_Request $request) {
807 760 try {
808 761 // Best-effort revoke at Google so the refresh token (which never
809 762 // auto-expires) can't keep querying on the admin's behalf after
810 763 // disconnect. Failure here must not block local cleanup.
@@ -897,9 +850,9 @@
897 850 * Writing provider API keys, disconnecting Google (a server-side token
898 851 * revoke) and running live connection tests manage the site's third-party
899 852 * credentials, so they require an administrator — `thinkrank_settings` is
900 853 * delegatable to non-admin roles through the Role Manager. Mirrors the
901 - * pattern used by the brand-visibility and AI-insights key writes.
854 + * pattern used by the AI-insights settings writes.
902 855 *
903 856 * @since 1.29.0
904 857 * @return bool Permission status
905 858 */
@@ -906,80 +859,8 @@
906 859 public function check_credential_permissions(): bool {
907 860 return current_user_can('manage_options');
908 861 }
909 862
910 - /**
911 - * Verify GA4 tracking
912 - * Following ThinkRank API response patterns
913 - *
914 - * @since 1.0.0
915 - * @param WP_REST_Request $request Request object
916 - * @return WP_REST_Response|WP_Error Response object
917 - */
918 - public function verify_ga4_tracking(WP_REST_Request $request): WP_REST_Response|WP_Error {
919 - try {
920 - // Empty is allowed and meaningful: verify_tracking() then reads the
921 - // homepage and discovers whichever GA4 ID is actually serving. The
922 - // old 400 made verification impossible on OAuth-connected sites
923 - // that never typed an ID in — precisely the reported case (#250).
924 - $measurement_id = (string) ( $request->get_param('measurement_id') ?? '' );
925 -
926 - // Load tracking manager
927 - if (!class_exists('ThinkRank\\Frontend\\Google_Analytics_Tracking_Manager')) {
928 - require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/class-google-analytics-tracking-manager.php';
929 - }
930 -
931 - $tracking_manager = new \ThinkRank\Frontend\Google_Analytics_Tracking_Manager();
932 - $verification_result = $tracking_manager->verify_tracking($measurement_id);
933 -
934 - return new WP_REST_Response([
935 - 'success' => true,
936 - 'data' => $verification_result,
937 - 'message' => 'Tracking verification completed'
938 - ], 200);
939 - } catch (\Exception $e) {
940 - return new WP_Error(
941 - 'verification_failed',
942 - 'Tracking verification failed: ' . $e->getMessage(),
943 - ['status' => 500]
944 - );
945 - }
946 - }
947 -
948 - /**
949 - * Detect GA4 conflicts
950 - * Following ThinkRank API response patterns
951 - *
952 - * @since 1.0.0
953 - * @param WP_REST_Request $request Request object
954 - * @return WP_REST_Response|WP_Error Response object
955 - */
956 - public function detect_ga4_conflicts(WP_REST_Request $request): WP_REST_Response|WP_Error {
957 - try {
958 - // Load tracking manager
959 - if (!class_exists('ThinkRank\\Frontend\\Google_Analytics_Tracking_Manager')) {
960 - require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/class-google-analytics-tracking-manager.php';
961 - }
962 -
963 - $tracking_manager = new \ThinkRank\Frontend\Google_Analytics_Tracking_Manager();
964 - $conflicts = $tracking_manager->detect_existing_tracking();
965 -
966 - return new WP_REST_Response([
967 - 'success' => true,
968 - 'data' => [
969 - 'conflicts' => $conflicts,
970 - 'has_conflicts' => !empty($conflicts)
971 - ],
972 - 'message' => 'Conflict detection completed'
973 - ], 200);
974 - } catch (\Exception $e) {
975 - return new WP_Error(
976 - 'conflict_detection_failed',
977 - 'Conflict detection failed: ' . $e->getMessage(),
978 - ['status' => 500]
979 - );
980 - }
981 - }
982 863 /**
983 864 * Fingerprint the currently connected Google account.
984 865 *
985 866 * Prefers the refresh token: it is issued once per authorization grant and