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 | media-optimization/class-smart-lazy-loader.php +46 -6 2.6.20 → 2.7.2 View file →
@@ -14,9 +14,8 @@
14 14
15 15 class Metasync_Smart_Lazy_Loader {
16 16
17 17 private array $settings;
18 - private int $img_counter = 0;
19 18
20 19 public function __construct(array $settings) {
21 20 $this->settings = $settings;
22 21
@@ -33,9 +32,9 @@
33 32 /**
34 33 * Process HTML content to add/manage lazy loading attributes.
35 34 */
36 35 public function process_content(string $content): string {
37 - if (empty($content) || is_admin() || is_feed()) {
36 + if (empty($content) || is_admin() || is_feed() || $this->is_amp_request()) {
38 37 return $content;
39 38 }
40 39
41 40 $content = $this->process_images($content);
@@ -53,11 +52,17 @@
53 52 private function process_images(string $content): string {
54 53 $skip = (int) ($this->settings['lcp_skip_count'] ?? 2);
55 54 $exclude_classes = array_filter(array_map('trim', explode(',', $this->settings['exclude_classes'] ?? '')));
56 55
57 - return preg_replace_callback('/<img\s[^>]+>/i', function ($matches) use ($skip, $exclude_classes) {
56 + // The LCP budget is scoped to a single content block (the same
57 + // semantics as core's wp_omit_loading_attr_threshold): an
58 + // instance-wide counter shared across the_content, thumbnails, and
59 + // widgets counted tags in filter-execution order, not document
60 + // order, so the hero could still end up lazy-loaded.
61 + $position = 0;
62 +
63 + return preg_replace_callback('/<img\s[^>]+>/i', function ($matches) use ($skip, $exclude_classes, &$position) {
58 64 $tag = $matches[0];
59 - $this->img_counter++;
60 65
61 66 // Already has a loading attribute - don't touch it
62 67 if (preg_match('/\sloading\s*=/i', $tag)) {
63 68 return $tag;
@@ -72,10 +77,14 @@
72 77 }
73 78 }
74 79 }
75 80
81 + // Only images this filter actually modifies consume the budget;
82 + // already-lazy and excluded tags above must not.
83 + $position++;
84 +
76 85 // Skip first N images to protect LCP
77 - if ($this->img_counter <= $skip) {
86 + if ($position <= $skip) {
78 87 return $this->inject_attribute($tag, 'loading', 'eager');
79 88 }
80 89
81 90 // Add loading="lazy" and decoding="async" for below-fold images
@@ -89,9 +98,15 @@
89 98 /**
90 99 * Add loading="lazy" to <iframe> tags.
91 100 */
92 101 private function process_iframes(string $content): string {
93 - return preg_replace_callback('/<iframe\s[^>]*>/i', function ($matches) {
102 + // Same skip semantics as images: the first N iframes (typically
103 + // above-fold embeds — a hero map or video) stay eager instead of
104 + // being lazy-loaded unconditionally.
105 + $skip = (int) ($this->settings['lcp_skip_count'] ?? 2);
106 + $position = 0;
107 +
108 + return preg_replace_callback('/<iframe\s[^>]*>/i', function ($matches) use ($skip, &$position) {
94 109 $tag = $matches[0];
95 110
96 111 if (preg_match('/\sloading\s*=/i', $tag)) {
97 112 return $tag;
@@ -96,10 +111,35 @@
96 111 if (preg_match('/\sloading\s*=/i', $tag)) {
97 112 return $tag;
98 113 }
99 114
115 + $position++;
116 + if ($position <= $skip) {
117 + return $this->inject_attribute($tag, 'loading', 'eager');
118 + }
119 +
100 120 return $this->inject_attribute($tag, 'loading', 'lazy');
101 121 }, $content);
122 + }
123 +
124 + /**
125 + * True when the current request renders an AMP page. The AMP runtime
126 + * manages its own loading strategy and rejects foreign attributes, so
127 + * this filter is skipped there. The helper indirection keeps static
128 + * analysis from assuming the AMP plugin's functions always exist.
129 + */
130 + private function is_amp_request(): bool {
131 + if (self::func_is_available('amp_is_request')) {
132 + return (bool) amp_is_request();
133 + }
134 + if (self::func_is_available('is_amp_endpoint')) {
135 + return (bool) is_amp_endpoint();
136 + }
137 + return false;
138 + }
139 +
140 + private static function func_is_available(string $function): bool {
141 + return function_exists($function);
102 142 }
103 143
104 144 /**
105 145 * Inject an HTML attribute into a tag if not already present.