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 +42 -103 2.6.24 → 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
@@ -196,8 +99,21 @@
196 99
197 100 # render route html
198 101 function render_route_html(){
199 102
103 + # Headless frontends do not consume WordPress-rendered HTML, so avoid
104 + # suggestion lookups, buffering and internal fetches altogether.
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) {
113 + return;
114 + }
115 +
200 116 # Clear per-request statics before any path runs. Harmless under PHP-FPM
201 117 # (the process ends with the request) but required under persistent-worker
202 118 # SAPIs, where a latched flag would silently disable cache capping and
203 119 # failure reporting for every later request in that worker.
@@ -310,8 +226,30 @@
310 226
311 227 # Get cache status for headers
312 228 $cache_status = Metasync_Otto_Transient_Cache::get_cache_status($cache_track_key);
313 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 +
314 252 # RENDER PRIORITY:
315 253 # 1. WP Rocket rocket_buffer filter — WP Rocket caches OTTO-modified HTML,
316 254 # so Kinsta also caches the correct version. No exit(), no cache bypass.
317 255 # 2. Output Buffer (fast) — for environments without WP Rocket.
@@ -327,9 +265,9 @@
327 265 # WP ROCKET PATH: highest priority when WP Rocket is active.
328 266 # rocket_buffer fires inside WP Rocket's own cache pipeline, so the
329 267 # cache file WP Rocket writes (and that Kinsta stores) is already
330 268 # OTTO-modified. Eliminates the race where WP Rocket saved pre-OTTO HTML.
331 - if (class_exists('WP_Rocket')) {
269 + if (defined('WP_ROCKET_VERSION') || class_exists('WP_Rocket')) {
332 270 $wp_rocket_compat_mode = Metasync_Otto_Config::get_wp_rocket_compat_mode();
333 271 if ($wp_rocket_compat_mode !== 'http') {
334 272 # disable_otto exits in metasync_start_otto() before we get here, so
335 273 # only 'http' compat mode needs to skip the rocket_buffer path.
@@ -827,8 +765,9 @@
827 765 Metasync_Otto_Render_Strategy::block_http_cache();
828 766 }
829 767
830 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
831 770 echo $route_html_string;
832 771
833 772 # prevent further wp execution
834 773 exit();