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 / media-optimization / class-smart-lazy-loader.php

class-smart-lazy-loader.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.18, at media-optimization/class-smart-lazy-loader.php

115 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Metasync_Smart_Lazy_Loader
4 * Adds loading="lazy" to images and iframes while protecting LCP elements.
5 *
6 * @package Search Atlas SEO
7 * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com
8 * @since 2.6.0
9 */
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 class Metasync_Smart_Lazy_Loader {
16
17 private array $settings;
18 private int $img_counter = 0;
19
20 public function __construct(array $settings) {
21 $this->settings = $settings;
22
23 // Tell WordPress to skip lazy loading for the first N images (WP 5.9+)
24 $skip = (int) ($settings['lcp_skip_count'] ?? 2);
25 add_filter('wp_omit_loading_attr_threshold', fn() => $skip);
26
27 // For broader control (including non-WP images and iframes), filter content
28 add_filter('the_content', [$this, 'process_content'], 25);
29 add_filter('post_thumbnail_html', [$this, 'process_content'], 25);
30 add_filter('widget_text', [$this, 'process_content'], 25);
31 }
32
33 /**
34 * Process HTML content to add/manage lazy loading attributes.
35 */
36 public function process_content(string $content): string {
37 if (empty($content) || is_admin() || is_feed()) {
38 return $content;
39 }
40
41 $content = $this->process_images($content);
42
43 if (!empty($this->settings['lazy_load_iframes'])) {
44 $content = $this->process_iframes($content);
45 }
46
47 return $content;
48 }
49
50 /**
51 * Add loading="lazy" to <img> tags, skipping the first N for LCP.
52 */
53 private function process_images(string $content): string {
54 $skip = (int) ($this->settings['lcp_skip_count'] ?? 2);
55 $exclude_classes = array_filter(array_map('trim', explode(',', $this->settings['exclude_classes'] ?? '')));
56
57 return preg_replace_callback('/<img\s[^>]+>/i', function ($matches) use ($skip, $exclude_classes) {
58 $tag = $matches[0];
59 $this->img_counter++;
60
61 // Already has a loading attribute - don't touch it
62 if (preg_match('/\sloading\s*=/i', $tag)) {
63 return $tag;
64 }
65
66 // Check excluded classes
67 if (!empty($exclude_classes) && preg_match('/class=["\']([^"\']+)["\']/i', $tag, $class_match)) {
68 $classes = explode(' ', $class_match[1]);
69 foreach ($exclude_classes as $excluded) {
70 if (in_array($excluded, $classes, true)) {
71 return $tag;
72 }
73 }
74 }
75
76 // Skip first N images to protect LCP
77 if ($this->img_counter <= $skip) {
78 return $this->inject_attribute($tag, 'loading', 'eager');
79 }
80
81 // Add loading="lazy" and decoding="async" for below-fold images
82 $tag = $this->inject_attribute($tag, 'loading', 'lazy');
83 $tag = $this->inject_attribute($tag, 'decoding', 'async');
84
85 return $tag;
86 }, $content);
87 }
88
89 /**
90 * Add loading="lazy" to <iframe> tags.
91 */
92 private function process_iframes(string $content): string {
93 return preg_replace_callback('/<iframe\s[^>]*>/i', function ($matches) {
94 $tag = $matches[0];
95
96 if (preg_match('/\sloading\s*=/i', $tag)) {
97 return $tag;
98 }
99
100 return $this->inject_attribute($tag, 'loading', 'lazy');
101 }, $content);
102 }
103
104 /**
105 * Inject an HTML attribute into a tag if not already present.
106 */
107 private function inject_attribute(string $tag, string $attr, string $value): string {
108 if (preg_match('/\s' . preg_quote($attr, '/') . '\s*=/i', $tag)) {
109 return $tag;
110 }
111
112 return preg_replace('/\s*\/?>$/', sprintf(' %s="%s"$0', esc_attr($attr), esc_attr($value)), $tag, 1);
113 }
114 }
115