├── README.md
├── phpunit.xml.dist
├── .gitignore
├── .editorconfig
├── composer.json
├── wp-lazy-loading.php
├── tests
└── phpunit
│ ├── bootstrap.php
│ └── tests
│ └── media.php
├── .travis.yml
├── readme.txt
├── functions.php
└── LICENSE
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.com/WordPress/wp-lazy-loading/)
2 | # Lazy Loading Feature Plugin
3 |
4 | WordPress feature plugin for testing and experimenting with automatically adding the "loading" HTML attribute.
5 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | ./tests/phpunit/tests/
20 |
21 |
22 |
23 |
24 |
25 | ./src
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ############
2 | ## IDEs
3 | ############
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | *.swp
9 | *~.nib
10 | local.properties
11 | .classpath
12 | .settings/
13 | .loadpath
14 | .externalToolBuilders/
15 | *.launch
16 | .cproject
17 | .buildpath
18 | nbproject/
19 |
20 | ############
21 | ## Vendor
22 | ############
23 |
24 | node_modules/
25 | vendor/
26 |
27 | ############
28 | ## OSes
29 | ############
30 |
31 | [Tt]humbs.db
32 | [Dd]esktop.ini
33 | *.DS_store
34 | .DS_store?
35 |
36 | ############
37 | ## Misc
38 | ############
39 |
40 | tests/logs
41 | bin/
42 | tmp/
43 | *.tmp
44 | *.bak
45 | *.log
46 | *.[Cc]ache
47 | *.cpr
48 | *.orig
49 | *.php.in
50 | .idea/
51 | .sass-cache/*
52 | temp/
53 | ._*
54 | .Trashes
55 | .svn
56 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # WordPress Coding Standards
2 | # https://make.wordpress.org/core/handbook/best-practices/coding-standards/
3 |
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | indent_size = 4
10 | tab_width = 4
11 | indent_style = tab
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.txt]
21 | trim_trailing_whitespace = false
22 |
23 | [*.json]
24 | insert_final_newline = false
25 | indent_style = space
26 | indent_size = 2
27 |
28 | [.*rc]
29 | insert_final_newline = false
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.yml]
34 | insert_final_newline = false
35 | indent_style = space
36 | indent_size = 2
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wordpress/wp-lazy-loading",
3 | "description": "WordPress feature plugin for testing and experimenting with automatically adding the loading HTML attribute.",
4 | "version": "1.0",
5 | "license": "GPL-2.0-or-later",
6 | "type": "wordpress-plugin",
7 | "keywords": [
8 | "feature plugin",
9 | "lazy loading"
10 | ],
11 | "homepage": "https://wordpress.org/plugins/wp-lazy-loading/",
12 | "support": {
13 | "issues": "https://github.com/WordPress/wp-lazy-loading/issues"
14 | },
15 | "config": {
16 | "platform": {
17 | "php": "5.6"
18 | }
19 | },
20 | "require": {
21 | "php": ">=5.6"
22 | },
23 | "require-dev": {
24 | "phpunit/phpunit": ">4.8.20 <6.0"
25 | },
26 | "scripts": {
27 | "test": "phpunit"
28 | }
29 | }
--------------------------------------------------------------------------------
/wp-lazy-loading.php:
--------------------------------------------------------------------------------
1 | array( basename( TESTS_PLUGIN_DIR ) . '/wp-lazy-loading.php' ),
41 | );
42 |
43 | // Load the WordPress tests environment.
44 | require_once $test_root . '/includes/bootstrap.php';
45 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | dist: xenial
3 | language: php
4 | services:
5 | - mysql
6 | cache:
7 | directories:
8 | - vendor
9 | - $HOME/.composer/cache
10 | matrix:
11 | include:
12 | - php: 7.4
13 | env: TEST=1 WP_VERSION=latest
14 | - php: 5.6
15 | env: TEST=1 WP_VERSION=latest
16 | dist: trusty
17 | - php: 7.4
18 | env: TEST=1 WP_VERSION=master
19 | allow_failures:
20 | - php: 7.4
21 | env: TEST=1 WP_VERSION=master
22 | before_install:
23 | - composer install
24 | before_script:
25 | - |
26 | if [[ "$TEST" == "1" ]]; then
27 | if [[ "$WP_VERSION" == "latest" ]]; then
28 | curl -s http://api.wordpress.org/core/version-check/1.7/ > /tmp/wp-latest.json
29 | WP_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
30 | fi
31 | PLUGIN_SLUG=$(basename $(pwd))
32 | export WP_DEVELOP_DIR=/tmp/wordpress/
33 | git clone --depth=50 --branch="$WP_VERSION" git://develop.git.wordpress.org/ /tmp/wordpress
34 | cd ..
35 | cp -r "$PLUGIN_SLUG" "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG"
36 | cd /tmp/wordpress/
37 | cp wp-tests-config-sample.php wp-tests-config.php
38 | sed -i "s/youremptytestdbnamehere/wordpress_tests/" wp-tests-config.php
39 | sed -i "s/yourusernamehere/travis/" wp-tests-config.php
40 | sed -i "s/yourpasswordhere//" wp-tests-config.php
41 | mysql -e "CREATE DATABASE wordpress_tests;" -uroot
42 | cd "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG"
43 | fi
44 | - phpenv rehash
45 | script:
46 | - |
47 | if [[ "$TEST" == "1" ]]; then
48 | composer run-script test
49 | fi
50 | notifications:
51 | email: false
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | === Lazy Loading Feature Plugin ===
2 | Contributors: wordpressdotorg, azaozz, flixos90
3 | Tags: feature plugin, lazy loading
4 | Requires at least: 5.3
5 | Tested up to: 5.4
6 | Stable tag: 1.1
7 | Requires PHP: 5.6.20
8 | License: GPLv2 or later
9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
10 |
11 | WordPress feature plugin for testing and experimenting with automatically adding the "loading" HTML attribute. Not for production use.
12 |
13 | == Description ==
14 |
15 | Lazy Loading Feature Plugin is an official plugin maintained by the WordPress team. It is intended for testing of automatically adding the `loading` HTML attribute to images and other elements that support it.
16 |
17 | More information about the `loading` attribute:
18 | Description: [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading).
19 | HTML Specification: [https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading](https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading).
20 |
21 | Currently the `loading` attribute is supported in the following browsers: [https://caniuse.com/#feat=loading-lazy-attr](https://caniuse.com/#feat=loading-lazy-attr).
22 |
23 | To test, install and enable the plugin. It will automatically add `loading="lazy"` attributes to all images in all new and existing posts, pages, and text widgets on the front-end.
24 |
25 | Then use one of the browsers that support it (Chrome, Opera, Firefox, Edge, Android, etc.) and visit the site. Best would be to test over a slower connection, with a phone, etc. and test web pages that have a lot of images, like gallery posts.
26 |
27 | = Things to look for =
28 |
29 | * Obvious bugs, for example images are missing.
30 | * Try to scroll down as soon as the page loads. All images should be at their places, and the page shouldn't "jump" when images are loaded.
31 |
32 | = Note to developers =
33 |
34 | This plugin is intended for testing. If the tests are successful, this functionality will be added to WordPress, but the exact code may change, perhaps significantly.
35 |
36 | When testing, please also test the filters added by this plugin, and provide feedback at [https://github.com/WordPress/wp-lazy-loading](https://github.com/WordPress/wp-lazy-loading) or at [https://core.trac.wordpress.org/ticket/44427](https://core.trac.wordpress.org/ticket/44427).
37 |
38 | == Changelog ==
39 |
40 | Please see the Github repository: [https://github.com/WordPress/wp-lazy-loading](https://github.com/WordPress/wp-lazy-loading).
41 |
--------------------------------------------------------------------------------
/tests/phpunit/tests/media.php:
--------------------------------------------------------------------------------
1 | attachment->create_upload_object( $filename );
13 | }
14 |
15 | public static function wpTearDownAfterClass() {
16 | wp_delete_post( self::$large_id, true );
17 | self::$large_id = null;
18 | }
19 |
20 | /**
21 | * @ticket 44427
22 | */
23 | function test_wp_lazy_load_content_media() {
24 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
25 | $img_xhtml = str_replace( ' />', '/>', $img );
26 | $img_html5 = str_replace( ' />', '>', $img );
27 | $iframe = '';
28 |
29 | $lazy_img = str_replace( '
', ' loading="eager" />', $img );
35 |
36 | $content = '
37 |
Image, standard.
38 | %1$s
39 |
40 | Image, XHTML 1.0 style (no space before the closing slash).
41 | %2$s
42 |
43 | Image, HTML 5.0 style.
44 | %3$s
45 |
46 | Image, with pre-existing "loading" attribute.
47 | %5$s
48 |
49 | Iframe, standard. Should not be modified.
50 | %4$s';
51 |
52 | $content_unfiltered = sprintf( $content, $img, $img_xhtml, $img_html5, $iframe, $img_eager );
53 | $content_filtered = sprintf( $content, $lazy_img, $lazy_img_xhtml, $lazy_img_html5, $iframe, $img_eager );
54 |
55 | // Do not add srcset and sizes while testing.
56 | add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
57 |
58 | $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
59 |
60 | remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
61 | }
62 |
63 | /**
64 | * @ticket 44427
65 | */
66 | function test_wp_lazy_load_content_media_opted_in() {
67 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
68 |
69 | $lazy_img = str_replace( '
Image, standard.
73 | %1$s';
74 |
75 | $content_unfiltered = sprintf( $content, $img );
76 | $content_filtered = sprintf( $content, $lazy_img );
77 |
78 | // Do not add srcset and sizes while testing.
79 | add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
80 |
81 | // Enable globally for all tags.
82 | add_filter( 'wp_lazy_loading_enabled', '__return_true' );
83 |
84 | $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
85 | remove_filter( 'wp_lazy_loading_enabled', '__return_true' );
86 | remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
87 | }
88 |
89 | /**
90 | * @ticket 44427
91 | */
92 | function test_wp_lazy_load_content_media_opted_out() {
93 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
94 |
95 | $content = '
96 | Image, standard.
97 | %1$s';
98 | $content = sprintf( $content, $img );
99 |
100 | // Do not add srcset and sizes while testing.
101 | add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
102 |
103 | // Disable globally for all tags.
104 | add_filter( 'wp_lazy_loading_enabled', '__return_false' );
105 |
106 | $this->assertSame( $content, wp_filter_content_tags( $content ) );
107 | remove_filter( 'wp_lazy_loading_enabled', '__return_false' );
108 | remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/functions.php:
--------------------------------------------------------------------------------
1 | ]+>/', $content, $matches ) ) {
129 | return $content;
130 | }
131 |
132 | // List of the unique `img` tags found in $content.
133 | $images = array();
134 |
135 | foreach ( $matches[0] as $image ) {
136 | if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) {
137 | $attachment_id = absint( $class_id[1] );
138 |
139 | if ( $attachment_id ) {
140 | /*
141 | * If exactly the same image tag is used more than once, overwrite it.
142 | * All identical tags will be replaced later with 'str_replace()'.
143 | */
144 | $images[ $image ] = $attachment_id;
145 | continue;
146 | }
147 | }
148 |
149 | $images[ $image ] = 0;
150 | }
151 |
152 | // Reduce the array to unique attachment IDs.
153 | $attachment_ids = array_unique( array_filter( array_values( $images ) ) );
154 |
155 | if ( count( $attachment_ids ) > 1 ) {
156 | /*
157 | * Warm the object cache with post and meta information for all found
158 | * images to avoid making individual database calls.
159 | */
160 | _prime_post_caches( $attachment_ids, false, true );
161 | }
162 |
163 | foreach ( $images as $image => $attachment_id ) {
164 | $filtered_image = $image;
165 |
166 | // Add 'srcset' and 'sizes' attributes if applicable.
167 | if ( $attachment_id > 0 && false === strpos( $filtered_image, ' srcset=' ) ) {
168 | $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
169 | }
170 |
171 | // Add 'loading' attribute if applicable.
172 | if ( $add_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) {
173 | $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context );
174 | }
175 |
176 | if ( $filtered_image !== $image ) {
177 | $content = str_replace( $image, $filtered_image, $content );
178 | }
179 | }
180 |
181 | return $content;
182 | }
183 |
184 | /**
185 | * Adds `loading` attribute to an existing `img` HTML tag.
186 | *
187 | * @since (TBD)
188 | *
189 | * @param string $image The HTML `img` tag where the attribute should be added.
190 | * @param string $context Additional context to pass to the filters.
191 | * @return string Converted `img` tag with `loading` attribute added.
192 | */
193 | function wp_img_tag_add_loading_attr( $image, $context ) {
194 | /**
195 | * Filters the `loading` attribute value. Default `lazy`.
196 | *
197 | * Returning `false` or an empty string will not add the attribute.
198 | * Returning `true` will add the default value.
199 | *
200 | * @since (TBD)
201 | *
202 | * @param string $value The 'loading' attribute value, defaults to `lazy`.
203 | * @param string $image The HTML 'img' element to be filtered.
204 | * @param string $context Additional context about how the function was called or where the img tag is.
205 | */
206 | $value = apply_filters( 'wp_img_tag_add_loading_attr', 'lazy', $image, $context );
207 |
208 | if ( $value ) {
209 | if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
210 | $value = 'lazy';
211 | }
212 |
213 | return str_replace( '
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------