PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.18
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.18
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 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / otto / Otto_pixel_class.php

Otto_pixel_class.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.18, at otto/Otto_pixel_class.php

658 lines 27.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // If this file is called directly, abort.
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * This class handles the Otto Pixel Functions
9 */
10 Class Metasync_otto_pixel{
11
12 #otto html class
13 public $o_html;
14
15 # crawl data option
16 public $option_name = 'metasync_otto_crawldata';
17
18 # no cache wp pages
19 public $no_cache_pages = ['wp-login.php'];
20
21 # OTTO UUID
22 private $otto_uuid;
23
24 #
25 function __construct($otto_uuid){
26
27 # store the UUID
28 $this->otto_uuid = $otto_uuid;
29
30 # load the html class
31 $this->o_html = new Metasync_otto_html($otto_uuid);
32
33 }
34
35 # method to handle cache refresh
36 # NOTE: Cache system removed - always process in real-time
37 function refresh_cache($route){
38 # No-op: Cache system has been removed
39 # All pages are processed in real-time from OTTO API
40 return true;
41 }
42
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 # get the current route
138 function get_route(){
139
140 # check if we're in an HTTP context
141 if(empty($_SERVER['HTTP_HOST']) || empty($_SERVER['REQUEST_URI'])){
142 # not in HTTP context (CLI, cron, etc.), return false
143 return false;
144 }
145
146 # get req scheme
147 $scheme = ( is_ssl() ? 'https' : 'http' );
148
149 # get req host
150 $host = $_SERVER['HTTP_HOST'];
151
152 # get the uri — strip query string before building the route.
153 # OTTO suggestions are canonical-URL-based; query parameters (Kinsta cache-bypass
154 # tokens, UTM params, tracking params, etc.) never produce different OTTO content.
155 # Stripping them ensures that:
156 # - The transient populated via a Kinsta BYPASS request (always has ?params) is
157 # shared with the clean-URL MISS request (no params).
158 # - The OTTO API is called with the canonical URL, not a noisy variant.
159 $request_uri = strtok($_SERVER['REQUEST_URI'], '?') ?: $_SERVER['REQUEST_URI'];
160
161 # return the formatted url
162 return $scheme . '://' . $host . $request_uri;
163 }
164
165 # get the html for a route
166 # OPTION 1 IMPLEMENTATION: Use transient cache for suggestions
167 function get_route_html($route, $cache_track_key = null, $suggestions = null){
168 # If suggestions already provided (from render_route_html), use them directly
169 if ($suggestions !== null && is_array($suggestions)) {
170 # Process route with provided suggestions data
171 return $this->o_html->process_route_with_data($route, $suggestions, '');
172 }
173
174 # Get OTTO UUID from options
175 global $metasync_options;
176 $otto_uuid = $metasync_options['general']['otto_pixel_uuid'] ?? '';
177
178 if (empty($otto_uuid)) {
179 return false;
180 }
181
182 # Get suggestions from transient cache (with API fallback)
183 $transient_cache = new Metasync_Otto_Transient_Cache($otto_uuid);
184 $track_key = $cache_track_key ?: md5($route);
185 $suggestions = $transient_cache->get_suggestions($route, $track_key);
186
187 if (!$suggestions || !$transient_cache->has_payload($suggestions)) {
188 # No suggestions available
189 return false;
190 }
191
192 # Process route with cached suggestions data
193 return $this->o_html->process_route_with_data($route, $suggestions, '');
194 }
195
196
197 # render route html
198 function render_route_html(){
199
200 # Disable SG Cache for Brizy pages FIRST - before any other processing
201 # Using global function defined in otto_pixel.php
202 if (function_exists('metasync_otto_disable_sg_cache_for_brizy')) {
203 metasync_otto_disable_sg_cache_for_brizy();
204 }
205
206 # get the route
207 $route = $this->get_route();
208
209 # get the current page from globas
210 # this is to help us exclude the login page
211
212 $page_now = $GLOBALS['pagenow'] ?? false;
213
214 # check whether page now in excluded pages
215 if(in_array($page_now , $this->no_cache_pages)){
216
217 # stop otto
218 return;
219 }
220
221 /*
222 #uncomment to test on local hosts
223 if(!empty($_GET['otto_test'])){
224
225 # @dev
226 $route = 'https://staging-perm.wp65.qa.internal.searchatlas.com/';
227 }
228 */
229
230 # OPTION 1 IMPLEMENTATION: Check transient cache instead of notification data
231 # This makes the system self-healing - works even if notifications fail
232
233 # Get OTTO UUID from options
234 global $metasync_options;
235 $otto_uuid = $metasync_options['general']['otto_pixel_uuid'] ?? '';
236
237 if (empty($otto_uuid)) {
238 return;
239 }
240
241 $transient_cache = new Metasync_Otto_Transient_Cache($otto_uuid);
242
243 # Create tracking key for cache status
244 $cache_track_key = 'otto_' . md5($route);
245
246 # Check if URL has OTTO suggestions (checks transient, calls API if needed)
247 # Use get_suggestions directly to track cache status
248 $suggestions = $transient_cache->get_suggestions($route, $cache_track_key);
249
250 if (!$suggestions || !$transient_cache->has_payload($suggestions)) {
251 # No suggestions available for this URL
252 # Set header to indicate cache status
253 if (!headers_sent()) {
254 $cache_status = Metasync_Otto_Transient_Cache::get_cache_status($cache_track_key);
255 header('X-MetaSync-OTTO-Cache: ' . ($cache_status ?: 'NO_SUGGESTIONS'));
256 header('X-MetaSync-OTTO-Method: NONE');
257 }
258 return;
259 }
260
261 # comment out for fixing pagination issues
262 # $route = rtrim($route, '/');
263
264 # Now that OTTO has confirmed suggestions to apply to this URL,
265 # disable SiteGround SG Optimizer page caching for THIS request only.
266 # Doing it here (instead of unconditionally on the `wp` hook) ensures
267 # pages without OTTO suggestions keep being served from SG's page cache.
268 if (function_exists('metasync_otto_disable_sg_page_cache')) {
269 metasync_otto_disable_sg_page_cache();
270 }
271
272 # Analyze what OTTO is providing for SEO plugin blocking
273 $blocking_flags = $this->analyze_otto_blocking($suggestions);
274
275 # Get cache status for headers
276 $cache_status = Metasync_Otto_Transient_Cache::get_cache_status($cache_track_key);
277
278 # RENDER PRIORITY:
279 # 1. WP Rocket rocket_buffer filter — WP Rocket caches OTTO-modified HTML,
280 # so Kinsta also caches the correct version. No exit(), no cache bypass.
281 # 2. Output Buffer (fast) — for environments without WP Rocket.
282 # 3. HTTP Request (last resort) — exits early, bypasses all caching.
283 try {
284 # Safety check: ensure render strategy class exists
285 if (!class_exists('Metasync_Otto_Render_Strategy')) {
286 # Class not loaded - use HTTP method as fallback
287 $this->render_via_http($route, $suggestions, $cache_track_key, $cache_status);
288 return;
289 }
290
291 # WP ROCKET PATH: highest priority when WP Rocket is active.
292 # rocket_buffer fires inside WP Rocket's own cache pipeline, so the
293 # cache file WP Rocket writes (and that Kinsta stores) is already
294 # OTTO-modified. Eliminates the race where WP Rocket saved pre-OTTO HTML.
295 if (class_exists('WP_Rocket')) {
296 $wp_rocket_compat_mode = Metasync_Otto_Config::get_wp_rocket_compat_mode();
297 if ($wp_rocket_compat_mode !== 'http') {
298 # disable_otto exits in metasync_start_otto() before we get here, so
299 # only 'http' compat mode needs to skip the rocket_buffer path.
300 $this->render_via_rocket_buffer($suggestions, $blocking_flags, $cache_status);
301 return;
302 }
303 }
304
305 $render_method = Metasync_Otto_Render_Strategy::determine_method();
306
307 if ($render_method === Metasync_Otto_Render_Strategy::METHOD_BUFFER) {
308 # FAST PATH: Use output buffer approach
309 $result = $this->render_via_buffer($route, $suggestions, $blocking_flags, $cache_status);
310
311 if ($result === true) {
312 # Buffer is now active, WordPress will continue rendering
313 # OTTO modifications will be applied when buffer flushes
314 return;
315 }
316
317 # Buffer approach failed, fall back to HTTP method
318 $render_method = Metasync_Otto_Render_Strategy::METHOD_HTTP;
319 }
320
321 if ($render_method === Metasync_Otto_Render_Strategy::METHOD_HTTP) {
322 # FALLBACK PATH: Use traditional HTTP request approach
323 $this->render_via_http($route, $suggestions, $cache_track_key, $cache_status);
324 }
325 } catch (Exception $e) {
326 # Any exception in the render strategy - fall back to HTTP method
327 $this->render_via_http($route, $suggestions, $cache_track_key, $cache_status);
328 } catch (Error $e) {
329 # PHP 7+ Error (like TypeError) - fall back to HTTP method
330 $this->render_via_http($route, $suggestions, $cache_track_key, $cache_status);
331 }
332 }
333
334 /**
335 * Render page using WP Rocket's rocket_buffer filter (PREFERRED for WP Rocket sites)
336 *
337 * When WP Rocket is active, hooking into rocket_buffer ensures WP Rocket
338 * caches the OTTO-modified HTML. This means:
339 * - WP Rocket's cache file = OTTO-modified HTML ✓
340 * - Kinsta FastCGI cache = WP Rocket output = OTTO-modified HTML ✓
341 * - No exit() = proper cache lifecycle ✓
342 *
343 * The filter fires on every uncached PHP request. Once WP Rocket has cached
344 * the result, subsequent requests are served directly from cache with zero PHP.
345 *
346 * @param array $suggestions OTTO suggestions data
347 * @param array $blocking_flags SEO plugin blocking flags
348 * @param string $cache_status Cache status for X-MetaSync-* headers
349 */
350 private function render_via_rocket_buffer($suggestions, $blocking_flags, $cache_status) {
351 $o_html = $this->o_html;
352
353 # Hook into WP Rocket's HTML buffer at priority 1 so other rocket_buffer
354 # filters (minifiers, CDN rewriters, etc.) run on already-OTTO-modified HTML.
355 add_filter('rocket_buffer', function($html) use ($o_html, $suggestions, $blocking_flags) {
356 if (empty($html) || strlen($html) < 100) {
357 // Tell WP Rocket not to cache this empty/broken response.
358 if (!defined('DONOTCACHEPAGE')) {
359 define('DONOTCACHEPAGE', true);
360 }
361 return $html;
362 }
363
364 # Only process full HTML documents — skip partials, JSON, error responses
365 if (stripos($html, '<html') === false && stripos($html, '<!DOCTYPE') === false) {
366 return $html;
367 }
368
369 # Pass blocking context to the HTML processor (suppresses Yoast/Rank Math tags
370 # that OTTO is replacing, preventing duplicates in the final HTML)
371 $data = $suggestions;
372 if (!empty($blocking_flags)) {
373 $data['_otto_blocking'] = $blocking_flags;
374 }
375
376 try {
377 $modified = $o_html->process_html_directly($html, $data);
378 # Sanity check: modified HTML must be at least 50% the size of original
379 if ($modified && strlen($modified) > strlen($html) * 0.5) {
380 return $modified;
381 }
382 } catch (Exception $e) {
383 // Fall through — return original HTML on any failure
384 } catch (Error $e) {
385 // Fall through — return original HTML on any failure
386 }
387
388 return $html;
389 }, 1);
390
391 # Block SEO plugins before wp_head fires so duplicate tags aren't output
392 $description_tags = $blocking_flags['block_description_tags'] ?? [];
393 $has_description_tags = !empty($description_tags);
394 if ($blocking_flags['block_title'] || $has_description_tags) {
395 if (function_exists('metasync_otto_block_seo_plugins')) {
396 metasync_otto_block_seo_plugins(
397 $blocking_flags['block_title'],
398 $has_description_tags,
399 $description_tags
400 );
401 }
402 }
403
404 # Send X-MetaSync-* diagnostic headers before WordPress outputs anything
405 if (!headers_sent()) {
406 Metasync_Otto_Render_Strategy::set_current_method(Metasync_Otto_Render_Strategy::METHOD_WP_ROCKET);
407 Metasync_Otto_Render_Strategy::send_headers($cache_status);
408 }
409
410 # Return — WordPress continues its normal render lifecycle.
411 # WP Rocket's buffer captures the full HTML, fires rocket_buffer,
412 # our callback modifies it, and WP Rocket caches the OTTO version.
413 }
414
415 /**
416 * Analyze OTTO suggestions to determine what to block from SEO plugins
417 *
418 * @param array $suggestions OTTO suggestions data
419 * @return array Blocking flags
420 */
421 private function analyze_otto_blocking($suggestions) {
422 $has_otto_title = false;
423 $otto_description_tags = []; // Track specific description tags Otto provides
424
425 if (!empty($suggestions['header_replacements']) && is_array($suggestions['header_replacements'])) {
426 foreach ($suggestions['header_replacements'] as $item) {
427 if (!empty($item['type'])) {
428 # Check if OTTO has title
429 if ($item['type'] == 'title' && !empty($item['recommended_value'])) {
430 $has_otto_title = true;
431 }
432 # Check if OTTO has description - track specific tag types
433 if ($item['type'] == 'meta') {
434 # Check for meta[name=description]
435 if (!empty($item['name']) && $item['name'] == 'description' && !empty($item['recommended_value'])) {
436 $otto_description_tags[] = 'meta[name=description]';
437 }
438 # Check for meta[property=og:description]
439 if (!empty($item['property']) && $item['property'] == 'og:description' && !empty($item['recommended_value'])) {
440 $otto_description_tags[] = 'meta[property=og:description]';
441 }
442 # Check for meta[name=twitter:description]
443 if (!empty($item['name']) && $item['name'] == 'twitter:description' && !empty($item['recommended_value'])) {
444 $otto_description_tags[] = 'meta[name=twitter:description]';
445 }
446 }
447 }
448 }
449 }
450
451 # Check header_html_insertion for description
452 # Must have a non-empty content value, otherwise Yoast would be blocked with nothing to replace it
453 if (!empty($suggestions['header_html_insertion'])) {
454 if (preg_match('/<meta[^>]*name=["\']description["\'][^>]*content=["\']([^"\']+)["\'][^>]*>/i', $suggestions['header_html_insertion'])) {
455 $otto_description_tags[] = 'meta[name=description]';
456 }
457 }
458
459 # Remove duplicates
460 $otto_description_tags = array_unique($otto_description_tags);
461
462 return [
463 'block_title' => $has_otto_title,
464 'block_description_tags' => $otto_description_tags, // Pass array of specific tags
465 ];
466 }
467
468 /**
469 * Render page using output buffer approach (FAST)
470 * Eliminates the internal HTTP request by capturing WordPress output directly
471 *
472 * @param string $route Current page route
473 * @param array $suggestions OTTO suggestions data
474 * @param array $blocking_flags SEO plugin blocking flags
475 * @param string $cache_status Cache status for headers
476 * @return bool True if buffer started successfully, false to fall back to HTTP
477 */
478 private function render_via_buffer($route, $suggestions, $blocking_flags, $cache_status) {
479 # Try to start output buffer
480 $buffer_started = Metasync_Otto_Render_Strategy::start_buffer(
481 $suggestions,
482 $route,
483 $this->o_html,
484 $blocking_flags
485 );
486
487 if (!$buffer_started) {
488 # Buffer failed to start
489 return false;
490 }
491
492 # Buffer is active - send headers now (before any output)
493 if (!headers_sent()) {
494 Metasync_Otto_Render_Strategy::send_headers($cache_status);
495 }
496
497 # Block SEO plugins if needed (for the buffered output)
498 $description_tags = $blocking_flags['block_description_tags'] ?? [];
499 $has_description_tags = !empty($description_tags);
500 if ($blocking_flags['block_title'] || $has_description_tags) {
501 if (function_exists('metasync_otto_block_seo_plugins')) {
502 metasync_otto_block_seo_plugins(
503 $blocking_flags['block_title'],
504 $has_description_tags,
505 $description_tags
506 );
507 }
508 }
509
510 # Return true - WordPress will continue rendering, buffer will capture and process
511 return true;
512 }
513
514 /**
515 * Render page using HTTP request approach (FALLBACK)
516 * Makes internal wp_remote_get request to fetch page HTML
517 *
518 * @param string $route Current page route
519 * @param array $suggestions OTTO suggestions data
520 * @param string $cache_track_key Cache tracking key
521 * @param string $cache_status Cache status for headers
522 */
523 private function render_via_http($route, $suggestions, $cache_track_key, $cache_status) {
524 # Set method indicator
525 Metasync_Otto_Render_Strategy::set_current_method(Metasync_Otto_Render_Strategy::METHOD_HTTP);
526
527 # On Divi sites, skip OTTO for the first HTTP render per page
528 # per 24h. OTTO's internal wp_remote_get creates corrupted CSS cache
529 # files in et-cache/{post_id}/ (wrong server context). By returning
530 # false once, WordPress renders the page normally — building correct
531 # Divi CSS caches. Subsequent OTTO renders within 24h use those caches.
532 # The transient stores the activation timestamp so it auto-invalidates
533 # when the plugin is deactivated/reactivated.
534 if (defined('ET_CORE_VERSION')) {
535 $cache_key = 'otto_divi_css_fix_' . md5($route);
536 $activated = get_option('metasync_activated_at', '');
537 $cached = get_transient($cache_key);
538 if ($cached === false || $cached !== $activated) {
539 set_transient($cache_key, $activated, DAY_IN_SECONDS);
540 return false;
541 }
542 }
543
544 # check if we have the route html (pass suggestions to avoid duplicate API call)
545 $route_html = $this->get_route_html($route, $cache_track_key, $suggestions);
546
547 # check that route html is valid
548 if(empty($route_html)){
549 return false;
550 }
551
552 $route_html_string = is_string($route_html) ? $route_html : $route_html->__toString();
553
554 # Detect incomplete Divi rendering in OTTO's HTTP render output.
555 # OTTO's internal wp_remote_get runs in a different server context than the
556 # normal page load. This can cause Divi to produce incomplete output:
557 #
558 # 1. Cold CSS cache: Divi outputs inline <style> blocks instead of external
559 # <link> tags (TB CSS files not yet generated)
560 # 2. Missing Google Fonts: Divi's font enqueue depends on request context
561 # (cookies, headers) that differ in the internal fetch
562 #
563 # If OTTO serves this incomplete HTML via exit(), SG Optimizer caches it —
564 # breaking CSS for all visitors until manual cache purge.
565 #
566 # Fix: skip OTTO for this request (return false), letting WordPress render
567 # normally. This builds Divi's caches so the next OTTO request works correctly.
568 if (defined('ET_CORE_VERSION')) {
569 # Check 1: Cold TB CSS cache (inline styles instead of external link)
570 if (preg_match('/<style\s[^>]*id=["\']et-core-unified-tb-[^"\']*-cached-inline-styles["\']/', $route_html_string)) {
571 return false;
572 }
573 # Check 2: Missing Google Fonts CSS (Divi enqueues this on every page)
574 # Normal render has: <link ... id='et-builder-googlefonts-cached-css' ...>
575 # or <link ... id='et-builder-googlefonts-css' ...>
576 # If neither is present, the internal fetch didn't load fonts properly.
577 if (strpos($route_html_string, 'et-builder-googlefonts') === false
578 && strpos($route_html_string, 'fonts.googleapis.com') === false
579 ) {
580 return false;
581 }
582 }
583
584 if (strpos($route_html_string, 'pix-sliding-headline-2') !== false || strpos($route_html_string, 'pix-intro-sliding-text') !== false) {
585 # Only apply fix within sliding text contexts to avoid breaking other layouts
586 $route_html_string = preg_replace('#(</span></span>)(<span\s+class=["\'][^"\']*slide-in-container[^"\']*["\'][^>]*>)#i', '$1 $2', $route_html_string);
587 }
588
589 # Fix for Elementor widgets - preserve whitespace between inline spans
590 # This prevents text/elements from appearing merged when HTML is minified
591
592 # Fix for Elementor social icons (elementor-grid-item)
593 if (strpos($route_html_string, 'elementor-social-icons-wrapper') !== false) {
594 # Add whitespace between closing and opening span tags within elementor-grid-item
595 $route_html_string = preg_replace(
596 '#(</span>)(<span\s+class=["\'][^"\']*elementor-grid-item[^"\']*["\'][^>]*>)#i',
597 '$1 $2',
598 $route_html_string
599 );
600 }
601
602 # Fix for Elementor animated headline (elementor-headline-text-wrapper)
603 if (strpos($route_html_string, 'elementor-headline') !== false) {
604 # Add whitespace between closing and opening span tags with elementor-headline-text-wrapper
605 $route_html_string = preg_replace(
606 '#(</span>)(<span\s+class=["\'][^"\']*elementor-headline-text-wrapper[^"\']*["\'][^>]*>)#i',
607 '$1 $2',
608 $route_html_string
609 );
610 }
611
612 # Check for Revolution Slider to determine if special handling is needed
613 # Check for both Revolution Slider 6 (<rs-module-wrap>) and Revolution Slider 7 (<sr7-module>)
614 $has_revslider = (strpos($route_html_string, '<rs-module-wrap') !== false || strpos($route_html_string, '<sr7-module') !== false);
615
616 if($has_revslider){
617 # Revolution Slider detected - fire WordPress hooks to ensure proper initialization
618 # Use output buffering to prevent hooks from corrupting Otto's processed HTML
619 ob_start();
620 do_action('wp_enqueue_scripts');
621 $discarded_output = ob_get_clean();
622
623 }
624
625 # If Divi's module-design CSS is missing from OTTO output,
626 # fetch it directly and inject it. This handles the case where the
627 # internal wp_remote_get response was truncated by SG Optimizer's parser.
628 if (strpos($route_html_string, 'et-builder-module-design') === false
629 && function_exists('et_theme_builder_decorate_page_resource_slug')
630 && function_exists('et_core_page_resource_get')
631 ) {
632 # Try to get Divi's inline CSS from its page resource manager
633 $post_id = get_the_ID();
634 if ($post_id) {
635 $resource_slug = et_theme_builder_decorate_page_resource_slug($post_id, 'module-design');
636 $manager = et_core_page_resource_get('builder', $resource_slug, $post_id, 40);
637 if ($manager && method_exists($manager, 'get_data')) {
638 $css_data = $manager->get_data('inline');
639 if (!empty($css_data)) {
640 $style_tag = '<style id="et-builder-' . esc_attr($resource_slug) . '-cached-inline-styles">'
641 . wp_strip_all_tags($css_data) . '</style>';
642 $route_html_string = str_replace('</body>', $style_tag . "\n" . '</body>', $route_html_string);
643 }
644 }
645 }
646 }
647
648 # Send response headers
649 Metasync_Otto_Render_Strategy::send_headers($cache_status);
650
651 # continue to render the html
652 echo $route_html_string;
653
654 # prevent further wp execution
655 exit();
656 }
657
658 }