PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | otto/otto_pixel.php +68 -29 2.7.1 → 2.7.2 View file →
@@ -44,8 +44,9 @@
44 44 require_once plugin_dir_path( __FILE__ ) . '/Otto_pixel_class.php';
45 45 require_once plugin_dir_path( __FILE__ ) . '/metasync-otto-seo-functions.php';
46 46 require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-transient-cache.php';
47 47 require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-job-status.php';
48 +require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-webhook-audit.php';
48 49 require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-render-strategy.php';
49 50 require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-config.php';
50 51 require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-bot-detector.php';
51 52 require_once plugin_dir_path( __FILE__ ) . '/class-metasync-otto-bot-statistics-database.php';
@@ -113,8 +114,9 @@
113 114 # OPTIMIZED: use cached uuid
114 115 $otto_tag = '<meta name="otto" content="uuid='.esc_attr(Metasync_Otto_Config::get_otto_uuid()).'; type=wordpress; enabled='.esc_attr($string_enabled).'; version='.esc_attr($plugin_version).'">';
115 116
116 117 # out the otto tag
118 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- meta tag assembled on the line above with esc_attr() on every interpolated value
117 119 echo $otto_tag;
118 120 }, 1); # Priority 1 to output early in head
119 121
120 122 /**
@@ -165,8 +167,13 @@
165 167
166 168 # function to register the route
167 169 function metasync_otto_crawl_notify($request){
168 170
171 + # receipt time is captured before validation and scheduling, so the audit
172 + # record describes when WordPress received the delivery, not when its work
173 + # happened to finish.
174 + $received_at = time();
175 +
169 176 # get request data params
170 177 $data = $request->get_json_params();
171 178
172 179 # fields
@@ -186,14 +193,29 @@
186 193 }
187 194
188 195 }
189 196
190 - # load otto pixel
191 - $otto_pixel = new Metasync_otto_pixel(false);
197 + # The audit trail is deliberately after payload validation: invalid requests
198 + # receive the existing 400 response and are not recorded as deliveries.
199 + # It is also before any scheduling so a valid delivery is captured even if
200 + # later work is deferred or rejected. The failure guard is intentionally
201 + # broader than Exception: a broken option adapter or stale plugin copy must
202 + # never turn an otherwise acknowledged webhook into a 500 response.
203 + try {
204 + if (metasync_otto_class_provides('Metasync_Otto_Webhook_Audit', 'record')) {
205 + Metasync_Otto_Webhook_Audit::record(
206 + $received_at,
207 + $data['domain'],
208 + $data['urls']
209 + );
210 + }
211 + } catch (Throwable $metasync_otto_audit_failure) {
212 + error_log(
213 + 'MetaSync OTTO: failed to record crawl-notify webhook audit entry: '
214 + . $metasync_otto_audit_failure->getMessage()
215 + ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- audit failure must not break webhook acknowledgement
216 + }
192 217
193 - # save the otto data first (cheap local DB write — kept synchronous)
194 - $otto_pixel->save_crawl_data($data);
195 -
196 218 # Defer the expensive per-URL work (OTTO API fetch, post meta sync,
197 219 # per-URL cache purge) to background jobs. The webhook caller enforces a
198 220 # strict response-time budget, so the response acknowledges SCHEDULING,
199 221 # never execution. The per-URL status store records what actually
@@ -424,10 +446,32 @@
424 446 }
425 447
426 448 Metasync_Otto_Job_Status::record($route, Metasync_Otto_Job_Status::STATE_COMPLETED, 'job', $retry_count, $outcome);
427 449
428 - } catch (Exception $e) {
429 - $schedule_retry('exception:' . $e->getMessage());
450 + } catch (\Throwable $e) {
451 + # Cron must survive both Exception and PHP 7+ Error failures. Always use
452 + # the same bounded retry path, and emit structured context without the URL
453 + # payload or credentials. The retry is booked BEFORE the log attempt so a
454 + # failing logger can never swallow the retry; logging is best-effort.
455 + $schedule_retry('throwable:' . get_class($e));
456 + if (class_exists('Metasync_Error_Logger')) {
457 + try {
458 + Metasync_Error_Logger::log(
459 + Metasync_Error_Logger::CATEGORY_OTTO_RENDER,
460 + Metasync_Error_Logger::SEVERITY_ERROR,
461 + 'OTTO crawl-url job threw; retry policy applied',
462 + [
463 + 'operation' => 'crawl_url_job',
464 + 'outcome' => 'throwable',
465 + 'retry_count' => (int) $retry_count,
466 + 'exception' => get_class($e),
467 + ]
468 + );
469 + } catch (\Throwable $logger_error) {
470 + # Logging must never kill the handler after the retry is booked.
471 + error_log('MetaSync OTTO: crawl-url job error log failed: ' . $logger_error->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
472 + }
473 + }
430 474 }
431 475 }
432 476
433 477 /**
@@ -889,9 +933,9 @@
889 933 return;
890 934 }
891 935
892 936 # OPTIMIZED: Check if Otto should be disabled for WP Rocket compatibility
893 - if (class_exists('WP_Rocket')) {
937 + if (defined('WP_ROCKET_VERSION') || class_exists('WP_Rocket')) {
894 938 $wp_rocket_compat_mode = Metasync_Otto_Config::get_wp_rocket_compat_mode();
895 939
896 940 if ($wp_rocket_compat_mode === 'disable_otto') {
897 941 return; # Exit early, Otto is disabled when WP Rocket is active
@@ -897,14 +941,25 @@
897 941 return; # Exit early, Otto is disabled when WP Rocket is active
898 942 }
899 943 }
900 944
901 - # Skip OTTO for Divi AJAX pagination and paginated archive requests.
902 - # ?et_blog = Divi AJAX pagination callback
945 + # Skip OTTO for Divi AJAX pagination, and for paginated archive requests
946 + # on Divi-built sites.
947 + # ?et_blog = Divi AJAX pagination callback — always skipped, on every
948 + # theme, because Divi fires it regardless of the site layout.
903 949 # /page/N/ = paginated blog/archive pages — OTTO's buffer/HTTP render causes
904 950 # module numbering mismatch between page 1 (with TB template) and page N
905 951 # (without TB template), breaking Divi's JS pagination selector matching.
906 - if (isset($_GET['et_blog']) || (is_paged() && !is_singular())) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only front-end check that skips OTTO
952 + # That mismatch is a Divi rendering bug, so the /page/N/ skip is gated on
953 + # Divi detection: non-Divi sites (Elementor + the7, ...) keep OTTO on real
954 + # paginated archives. Detection covers the Divi theme and its child themes
955 + # (get_template() returns the PARENT template dir, "Divi" either way), the
956 + # Extra theme and the Divi Builder plugin (both define ET_BUILDER_VERSION
957 + # / et_setup_theme — same TB machinery, same bug).
958 + $is_divi = defined('ET_BUILDER_VERSION')
959 + || function_exists('et_setup_theme')
960 + || wp_get_theme()->get_template() === 'Divi';
961 + if (isset($_GET['et_blog']) || ($is_divi && is_paged() && !is_singular())) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only front-end check that skips OTTO
907 962 return;
908 963 }
909 964
910 965 # Skip OTTO on search-results pages. A search request carries its
@@ -1047,9 +1102,9 @@
1047 1102 # Detect active plugins. SG Optimizer detection now lives in
1048 1103 # metasync_otto_disable_sg_page_cache(), which is invoked later
1049 1104 # once OTTO confirms suggestions for the URL.
1050 1105 $brizy_active = class_exists('Brizy_Editor') || defined('BRIZY_VERSION');
1051 - $wp_rocket_active = class_exists('WP_Rocket');
1106 + $wp_rocket_active = defined('WP_ROCKET_VERSION') || class_exists('WP_Rocket');
1052 1107
1053 1108 # OPTIMIZED: Get configuration option using cached config
1054 1109 $wp_rocket_compat_mode = Metasync_Otto_Config::get_wp_rocket_compat_mode();
1055 1110
@@ -1122,9 +1177,9 @@
1122 1177 * Only meaningful on SiteGround sites without WP Rocket; a no-op otherwise.
1123 1178 */
1124 1179 function metasync_otto_disable_sg_page_cache() {
1125 1180 # Only relevant when SG Optimizer is active and WP Rocket is not present.
1126 - if (class_exists('WP_Rocket')) {
1181 + if (defined('WP_ROCKET_VERSION') || class_exists('WP_Rocket')) {
1127 1182 return;
1128 1183 }
1129 1184 if (!is_plugin_active('sg-cachepress/sg-cachepress.php')) {
1130 1185 return;
@@ -1849,24 +1904,8 @@
1849 1904 metasync_record_failed_action( 'metasync_process_seo_job' );
1850 1905 return Metasync_Otto_Job_Status::OUTCOME_RETRYABLE;
1851 1906 }
1852 1907 }
1853 -
1854 - # Mark this URL as crawled by OTTO for SSR
1855 - # Extract domain and path from route
1856 - $parsed_url = wp_parse_url($route);
1857 - $domain_with_scheme = ($parsed_url['scheme'] ?? 'https') . '://' . ($parsed_url['host'] ?? '');
1858 - $url_path = ($parsed_url['path'] ?? '/');
1859 -
1860 - # Create crawl data structure
1861 - $crawl_data = array(
1862 - 'domain' => $domain_with_scheme,
1863 - 'urls' => array($url_path)
1864 - );
1865 -
1866 - # Load Otto pixel class and save crawl data
1867 - $otto_pixel = new Metasync_otto_pixel($otto_uuid);
1868 - $otto_pixel->save_crawl_data($crawl_data);
1869 1908
1870 1909 # Get WordPress post ID from URL
1871 1910 $post_id = url_to_postid($route);
1872 1911