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_class.php +37 -104 2.7.0 → 2.7.2 View file →
@@ -7,27 +7,24 @@
7 7 /**
8 8 * This class handles the Otto Pixel Functions
9 9 */
10 10 Class Metasync_otto_pixel{
11 -
11 +
12 12 #otto html class
13 13 public $o_html;
14 14
15 - # crawl data option
16 - public $option_name = 'metasync_otto_crawldata';
17 -
18 - # no cache wp pages
15 + # no cache wp pages
19 16 public $no_cache_pages = ['wp-login.php'];
20 17
21 18 # OTTO UUID
22 19 private $otto_uuid;
23 20
24 - #
21 + #
25 22 function __construct($otto_uuid){
26 -
23 +
27 24 # store the UUID
28 25 $this->otto_uuid = $otto_uuid;
29 -
26 +
30 27 # load the html class
31 28 $this->o_html = new Metasync_otto_html($otto_uuid);
32 29
33 30 }
@@ -39,102 +36,8 @@
39 36 # All pages are processed in real-time from OTTO API
40 37 return true;
41 38 }
42 39
43 -
44 - # method to save crawl data into
45 - function save_crawl_data($data){
46 -
47 - # get the option name
48 - $option_name = $this->option_name;
49 -
50 - # handle data
51 - $saved = get_option($option_name);
52 -
53 - # log saved
54 -
55 - # if saved false save
56 - if(empty($saved['urls'])){
57 -
58 - # save the option
59 - update_option($option_name, $data);
60 -
61 - return;
62 - }
63 -
64 - # get new unique list of urls
65 - $new_list = array_unique(array_merge($data['urls'], $saved['urls']));
66 -
67 - # set data
68 - $data['urls'] = $new_list;
69 -
70 - # log saved
71 -
72 - # save the option
73 - update_option($option_name, $data);
74 - }
75 -
76 - /**
77 - * Check if a URL has been crawled by Otto
78 - * - Validates domain against saved domain
79 - * - Ignores query strings
80 - * - Does exact path matching as Otto stores paths exactly as they appear
81 - * @param string $url - Full URL to check
82 - * @return bool - True if URL was crawled, false otherwise
83 - */
84 - function is_url_crawled($url) {
85 - # Ensure valid input
86 - if (empty($url) || !is_string($url)) {
87 - return false;
88 - }
89 -
90 - # Get saved crawl data
91 - $saved = get_option($this->option_name);
92 -
93 - if (
94 - empty($saved) ||
95 - !is_array($saved) ||
96 - empty($saved['domain']) ||
97 - empty($saved['urls']) ||
98 - !is_array($saved['urls'])
99 - ) {
100 - return false;
101 - }
102 -
103 - # Parse saved domain + incoming URL
104 - $saved_domain = parse_url($saved['domain'], PHP_URL_HOST);
105 - $incoming_domain = parse_url($url, PHP_URL_HOST);
106 -
107 - # Domain mismatch - not crawled
108 - if (empty($saved_domain) || empty($incoming_domain) || strcasecmp($saved_domain, $incoming_domain) !== 0) {
109 - return false;
110 - }
111 -
112 - # Parse incoming path (ignore query string)
113 - $parsed_url = parse_url($url);
114 - $url_path = $parsed_url['path'] ?? '/';
115 -
116 - # Ensure path starts with / but don't modify trailing slashes
117 - # Otto stores paths exactly as they appear in URLs
118 - if (substr($url_path, 0, 1) !== '/') {
119 - $url_path = '/' . $url_path;
120 - }
121 -
122 - # Compare against crawled URLs - exact match
123 - foreach ($saved['urls'] as $crawled_url) {
124 - if (!is_string($crawled_url)) {
125 - continue;
126 - }
127 -
128 - # Direct comparison - Otto stores paths exactly as they are
129 - if (strcasecmp($crawled_url, $url_path) === 0) {
130 - return true;
131 - }
132 - }
133 -
134 - return false;
135 - }
136 -
137 40 # get the current route
138 41 function get_route(){
139 42
140 43 # check if we're in an HTTP context
@@ -198,9 +101,16 @@
198 101 function render_route_html(){
199 102
200 103 # Headless frontends do not consume WordPress-rendered HTML, so avoid
201 104 # suggestion lookups, buffering and internal fetches altogether.
202 - if (Metasync_Headless_Config::is_active()) {
105 + # Falls back to a guarded direct read rather than skipping the check:
106 + # silently skipping would render OTTO markup on a headless site.
107 + # @phpstan-ignore-next-line function.alreadyNarrowedType
108 + $headless = function_exists('metasync_headless_is_active')
109 + ? metasync_headless_is_active()
110 + : (class_exists('Metasync_Headless_Config') && Metasync_Headless_Config::is_active());
111 +
112 + if ($headless) {
203 113 return;
204 114 }
205 115
206 116 # Clear per-request statics before any path runs. Harmless under PHP-FPM
@@ -316,8 +226,30 @@
316 226
317 227 # Get cache status for headers
318 228 $cache_status = Metasync_Otto_Transient_Cache::get_cache_status($cache_track_key);
319 229
230 + # POSTMETA BACKSTOP: a MISS means this request already paid for a live
231 + # OTTO fetch, which makes it the cheapest possible place to repair the
232 + # stored SEO meta when the crawl-notify webhook never ran. The sync is
233 + # deferred to a shutdown callback that runs after the response HTML is
234 + # built, so the rendered page and its timing are untouched. A HIT never
235 + # reaches this branch — steady-state requests keep doing zero extra
236 + # work. The class_exists()/method_exists() pair mirrors the guard
237 + # above: during a plugin update an opcache can hold this file without
238 + # the new class, and an unguarded static call would fatal the page.
239 + # The whole hook sits in try/catch: in the mixed-opcache window a missing
240 + # class constant is an Error, and the backstop must never fatal the page.
241 + try {
242 + $is_cache_miss = ($cache_status === Metasync_Otto_Transient_Cache::STATUS_MISS);
243 + # @phpstan-ignore-next-line function.alreadyNarrowedType
244 + if ($is_cache_miss && class_exists('Metasync_Otto_Backstop_Sync') && method_exists('Metasync_Otto_Backstop_Sync', 'maybe_defer_sync')) {
245 + Metasync_Otto_Backstop_Sync::maybe_defer_sync($route, $suggestions, $cache_status);
246 + }
247 + } catch (Throwable $backstop_error) {
248 + // Best-effort repair only; rendering continues untouched.
249 + unset($backstop_error);
250 + }
251 +
320 252 # RENDER PRIORITY:
321 253 # 1. WP Rocket rocket_buffer filter — WP Rocket caches OTTO-modified HTML,
322 254 # so Kinsta also caches the correct version. No exit(), no cache bypass.
323 255 # 2. Output Buffer (fast) — for environments without WP Rocket.
@@ -333,9 +265,9 @@
333 265 # WP ROCKET PATH: highest priority when WP Rocket is active.
334 266 # rocket_buffer fires inside WP Rocket's own cache pipeline, so the
335 267 # cache file WP Rocket writes (and that Kinsta stores) is already
336 268 # OTTO-modified. Eliminates the race where WP Rocket saved pre-OTTO HTML.
337 - if (class_exists('WP_Rocket')) {
269 + if (defined('WP_ROCKET_VERSION') || class_exists('WP_Rocket')) {
338 270 $wp_rocket_compat_mode = Metasync_Otto_Config::get_wp_rocket_compat_mode();
339 271 if ($wp_rocket_compat_mode !== 'http') {
340 272 # disable_otto exits in metasync_start_otto() before we get here, so
341 273 # only 'http' compat mode needs to skip the rocket_buffer path.
@@ -833,8 +765,9 @@
833 765 Metasync_Otto_Render_Strategy::block_http_cache();
834 766 }
835 767
836 768 # continue to render the html
769 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- server-rendered route HTML from the OTTO render strategy; escaping would corrupt the document
837 770 echo $route_html_string;
838 771
839 772 # prevent further wp execution
840 773 exit();