WebP Express - Version 0.11.0

Version Description

  • Alter HTML to point to webp files (choose between picture tags or simply altering all image urls)
  • Convert non-existing webp-files upon request (means you can reference the converted webp files before they are actually converted!)

For more info, see the closed issues on the 0.11.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/14?closed=1

Download this release

Release Info

Developer rosell.dk
Plugin Icon 128x128 WebP Express
Version 0.11.0
Comparing to
See all releases

Code changes from version 0.10.0 to 0.11.0

Files changed (48) hide show
  1. README.md +133 -46
  2. README.txt +106 -48
  3. changelog.txt +6 -0
  4. composer.json +25 -3
  5. docs/regex.md +154 -0
  6. js/picturefill.min.js +5 -0
  7. lib/admin.php +3 -1
  8. lib/alter-html.php +75 -0
  9. lib/classes/AlterHtmlHelper.php +257 -0
  10. lib/classes/AlterHtmlImageUrls.php +83 -0
  11. lib/classes/AlterHtmlInit.php +126 -0
  12. lib/classes/AlterHtmlPicture.php +18 -0
  13. lib/classes/CacheMover.php +36 -5
  14. lib/classes/Config.php +90 -15
  15. lib/classes/FileHelper.php +52 -2
  16. lib/classes/HTAccess.php +131 -66
  17. lib/classes/Messenger.php +7 -1
  18. lib/classes/Paths.php +45 -15
  19. lib/classes/TestRun.php +2 -2
  20. lib/migrate/migrate.php +29 -16
  21. lib/migrate/migrate5.php +55 -0
  22. lib/options/css/webp-express-options-page.css +24 -2
  23. lib/options/enqueue_scripts.php +1 -1
  24. lib/options/js/page.js +35 -1
  25. lib/options/options/alter-html/alter-html-options.inc +98 -0
  26. lib/options/options/alter-html/alter-html.inc +19 -0
  27. lib/options/options/conversion-options/conversion-options.inc +1 -1
  28. lib/options/options/conversion-options/destination-extension.inc +24 -15
  29. lib/options/options/conversion-options/destination-folder.inc +24 -13
  30. lib/options/options/operation-mode.inc +24 -26
  31. lib/options/options/redirection-rules/enable-redirection-to-converter.inc +17 -3
  32. lib/options/options/redirection-rules/enable-redirection-to-webp-realizer.inc +26 -0
  33. lib/options/options/redirection-rules/image-types.inc +2 -2
  34. lib/options/options/redirection-rules/only-redirect-to-converter-on-cache-miss.inc +9 -1
  35. lib/options/options/redirection-rules/redirection-rules.inc +4 -2
  36. lib/options/options/serve-options/serve-options.inc +1 -1
  37. lib/options/page-messages.php +2 -2
  38. lib/options/page-welcome.php +1 -1
  39. lib/options/page.php +60 -2
  40. lib/options/submit.php +41 -17
  41. lib/uninstall.php +1 -1
  42. test/very-small.jpg +0 -0
  43. tests/BananaTest.php +24 -0
  44. webp-express.php +7 -1
  45. wod/.htaccess +1 -1
  46. wod/.webp +0 -0
  47. wod/webp-on-demand.php +31 -17
  48. wod/webp-realizer.php +215 -0
README.md CHANGED
@@ -1,22 +1,35 @@
1
# WebP Express
2
3
- Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library, galleries, theme images etc).
4
5
The plugin is available on the Wordpress codex ([here](https://wordpress.org/plugins/webp-express/)), and developed on github ([here](https://github.com/rosell-dk/webp-express/)).
6
7
## Description
8
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
9
10
- The plugin basically routes jpeg/png images to an image converter, or - if the image converter has already converted the image - directly to a converted image. The approach has the benefit that is works regardless of how an image found its way into your server - be it Media Library, Galleries, or even theme images referenced with CSS.
11
12
The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and its "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md)
13
14
- #### Benefits
15
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
16
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
17
- Better ranking in Google searches (performance is taken into account by Google)
18
- Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
19
- - Currently ~73% of all traffic, and ~78% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
20
21
22
## Installation
@@ -29,6 +42,22 @@ The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) an
29
### Configuring
30
You configure the plugin in *Settings > WebP Express*.
31
32
#### Conversion methods
33
WebP Express has a bunch of methods available for converting images: Executing cwebp binary, Gd extension, Imagick extension, ewww cloud converter and remote WebP express. Each requires *something*. In many cases, one of the conversion methods will be available. You can quickly identify which converters are working - there is a green icon next to them. Hovering conversion methods that are not working will show you what is wrong.
34
@@ -51,7 +80,7 @@ Once, you have a converter, that works, when you click the "test"-button, you ar
51
52
If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching. It could also be that your images are handled by nginx.
53
54
- Note that the plugin does not change any HTML. In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
55
56
- Open the page in Google Chrome
57
- Right-click the page and choose "Inspect"
@@ -66,7 +95,7 @@ You can also append `?debug` after any image url, in order to run a conversion,
66
### Notes
67
68
*Note:*
69
- The redirect rules created in *.htaccess* are pointing to a PHP script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
70
71
*Note:*
72
Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
@@ -108,12 +137,12 @@ Assuming that all above is in place, please look at the response headers to see
108
I shall write more on this FAQ item... Stay tuned.
109
110
### How can a webp image be served on an URL ending with "jpg"?
111
- Easy enough. Browsers looks at the *content type* header rather than the URL to determine what it is that it gets. So, although it can be confusing that the resource at *example.com/image.jpg* is a webp image, rest asure that the browsers are not confused. To determine if the plugin is working, you must therefore examine the *content type* response header rather than the URL. See the "How do I verify that the plugin is working?" Faq item.
112
113
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
114
115
### I am on NGINX / OpenResty
116
- It is possible to make WebP Express work on NGINX, but it requieres manually inserting redirection rules in the NGINX configuration file (nginx.conf or the configuration file for the site, found in `/etc/nginx/sites-available`). On most setups the following rules should work:
117
118
```
119
if ($http_accept ~* "webp"){
@@ -158,7 +187,7 @@ Well, there is no point in having the "Speed up image load times" enabled togeth
158
159
But there is a case for using WebP Express rather than Jetpacks "Speed up image load times" feature:
160
161
- Jetpack has the same drawback as the *Standard* WebP Express operation mode: If a user downloads the file, there will be a mismatch between the file extension and the image type (the file is ie called "logo.jpg", but it is really a webp image). I don't think that is a big issue, but for those who do, WebP Express might still be for you, even though you have Jetpack. And that is because WebP Express can be set up just to generate webp's, without doing the internal redirection to webp (will be possible from version 0.10.0). You can then for example use the [Cache Enabler](https://wordpress.org/plugins/cache-enabler/) plugin, which is able to generate and cache two versions of each page. One for browsers that accepts webp and one for those that don't. In the HTML for webp-enabled browsers, the images points directly to the webp files.
162
163
Pro Jetpack:
164
- It is a free CDN which serves webp out of the box.
@@ -183,12 +212,64 @@ If you have the *Imagick*, the *Imagick binary* or the *Remote WebP Express* con
183
184
Note: If you experience that the general auto option doesn't show, even though the above-mentioned requirements should be in order, check out [this support-thread](https://wordpress.org/support/topic/still-no-auto-option/).
185
186
- ### How do I make this work with a CDN?
187
- Chances are that the default setting of your CDN is not to forward any headers to your origin server. But the plugin needs the "Accept" header, because this is where the information is whether the browser accepts webp images or not. You will therefore have to make sure to configure your CDN to forward the "Accept" header.
188
189
- The plugin takes care of setting the "Vary" HTTP header to "Accept" when routing WebP images. When the CDN sees this, it knows that the response varies, depending on the "Accept" header. The CDN is thus instructed not to cache the response on URL only, but also on the "Accept" header. This means that it will store an image for every accept header it meets. Luckily, there are (not that many variants for images)[https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values#Values_for_an_image], so it is not an issue.
190
191
- In the 0.10.0 release, you will have another (and better) option, as [described here](https://github.com/rosell-dk/webp-express/issues/133)
192
193
### I am on Cloudflare
194
Without configuration, Cloudflare will not maintain separate caches for jpegs and webp; all browsers will get jpeg. To make Cloudflare cache not only by URL, but also by header, you need to use the [Custom Cache Key](https://support.cloudflare.com/hc/en-us/articles/115004290387) page rule, and add *Header content* to make separate caches depending on the *Accept* request header.
@@ -206,28 +287,26 @@ To make *WebP Express* work on a free Cloudflare account, you have the following
206
### WebP Express / ShortPixel setup
207
Here is a recipe for using WebP Express together with ShortPixel, such that WebP Express generates the webp's, and ShortPixel only is used to create `<picture>` tags, when it detects a webp image in the same folder as an original.
208
209
- The reason for doing this could be:
210
- 1. You are using a CDN which cannot be configured to work with the Standard WebP Express setup.
211
- 2. You think it is problematic that when a user saves an image, it has the jpg extension, even though it is a webp image.
212
213
You need:
214
1 x WebP Express
215
1 x ShortPixel
216
217
- #### 1. Setup WebP Express
218
- If you are using a CDN which cannot be configured to work in *Standard mode*:
219
- Open WebP Express options
220
- - Switch to *Just convert* mode.
221
- Set *File extension* to "Set to .webp"
222
- - Make sure the *Auto convert* option is enabled
223
224
If you want to *ShortPixel* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
225
- Open WebP Express options
226
- - Switch to *Standard* mode.
227
- Set *Destination folder* to "Mingled"
228
- Set *File extension* to "Set to .webp"
229
230
- #### 2. Setup ShortPixel
231
- Install [ShortPixel](https://wordpress.org/plugins/shortpixel-image-optimiser/) the usual way
232
- Get an API key and enter it on the options page.
233
- In *Advanced*, enable the following options:
@@ -238,13 +317,13 @@ If you want to *ShortPixel* to create <picture> tags but still want the magic to
238
- *Automatically optimize images added by users in front end.*
239
- *Automatically optimize Media Library items after they are uploaded (recommended).*
240
241
- #### 3. Visit a page
242
As there are presumably no webps generated yet, ShortPixel will not generate `<picture>` tags on the first visit. However, the images that are referenced causes the WebP Express *Auto convert* feature to kick in and generate webp images for each image on that page.
243
244
- #### 4. Visit the page again
245
As *WebP Express* have generated webps in the same folder as the originals, *ShortPixel* detects these, and you should see `<picture>` tags which references the webp's.
246
247
- #### ShortPixel or Cache Enabler ?
248
Cache Enabler has the advantage over ShortPixel that the HTML structure remains the same. With ShortPixel, image tags are wrapped in a `<picture>` tag structure, and by doing that, there is a risk of breaking styles.
249
250
Further, Cache Enabler *caches* the HTML. This is good for performance. However, this also locks you to using that plugin for caching. With ShortPixel, you can keep using your favourite caching plugin.
@@ -252,35 +331,39 @@ Further, Cache Enabler *caches* the HTML. This is good for performance. However,
252
Cache Enabler will not work if you are caching HTML on a CDN, because the HTML varies depending on the *Accept* header and it doesn't signal this with a Vary:Accept header. You could however add that manually. ShortPixel does not have that issue, as the HTML is the same for all.
253
254
### WebP Express / Cache Enabler setup
255
- The WebP Express / Cache Enabler setup is quite potent and very CDN-friendly. Cache Enabler is used for generating and caching two versions of the HTML (one for webp-enabled browsers and one for webp-disabled browsers)
256
257
The reason for doing this could be:
258
- 1. You are using a CDN which cannot be configured to work with the Standard WebP Express setup.
259
- 2. You think it is problematic that when a user saves an image, it has the jpg extension, even though it is a webp image.
260
261
You need:
262
1 x WebP Express
263
1 x Cache Enabler
264
265
- #### 1. Setup WebP Express
266
- If you are using a CDN which cannot be configured to work in *Standard mode*:
267
- Open WebP Express options
268
- - Switch to *Just convert* mode.
269
- Set *File extension* to "Set to .webp"
270
- - Make sure the *Auto convert* option is enabled
271
272
If you want to *Cache Enabler* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
273
- Open WebP Express options
274
- - Switch to *Standard* mode.
275
- Set *Destination folder* to "Mingled"
276
- Set *File extension* to "Set to .webp"
277
278
- #### 2. Setup Cache Enabler
279
- Open the options
280
- Enable of the *Create an additional cached version for WebP image support* option
281
282
- #### 3. Let rise in a warm place until doubled
283
- *WebP Express* creates *webp* images on need basis. It needs page visits in order to do the convertions . Bulk conversion is on the roadmap, but until then, you need to visit all pages of relevance. You can either do it manually, let your visitors do it (that is: wait a bit), or, if you are on linux, you can use `wget` to grab your website:
284
285
```
286
wget -e robots=off -r -np -w 2 http://www.example.com
@@ -291,24 +374,18 @@ wget -e robots=off -r -np -w 2 http://www.example.com
291
`-np` (no-parent) makes wget stay within the boundaries (doesn't go into parent folders)
292
`w 2` Waits two seconds between each request, in order not to stress the server
293
294
- #### 4. Clear the Cache Enabler cache.
295
Click the "Clear Cache" button in the top right corner in order to clear the Cache Enabler cache.
296
297
- #### 5. Inspect the HTML
298
When visiting a page with images on, different HTML will be served to browsers, depending on whether they support webp or not.
299
300
In a webp-enabled browser, the HTML may look like this: `<img src="image.webp">`, while in a non-webp enabled browser, it looks like this: `<img src="image.jpg">`
301
302
303
- #### 6. Optionally add Cache Enabler rewrite rules in your .htaccess
304
*Cache Enabler* provides some rewrite rules that redirects to the cached file directly in the `.htaccess`, bypassing PHP entirely. Their plugin doesn't do that for you, so you will have to do it manually in order to get the best performance. The rules are in the "Advanced configuration" section on [this page](https://www.keycdn.com/support/wordpress-cache-enabler-plugin).
305
306
- #### ShortPixel or Cache Enabler ?
307
- Cache Enabler has the advantage over ShortPixel that the HTML structure remains the same. With ShortPixel, image tags are wrapped in a `<picture>` tag structure, and by doing that, there is a risk of breaking styles.
308
-
309
- Further, Cache Enabler *caches* the HTML. This is good for performance. However, this also locks you to using that plugin for caching. With ShortPixel, you can keep using your favourite caching plugin.
310
-
311
- Cache Enabler will not work if you are caching HTML on a CDN, because the HTML varies depending on the *Accept* header and it doesn't signal this with a Vary:Accept header. You could however add that manually. ShortPixel does not have that issue, as the HTML is the same for all.
312
313
### Does it work with lazy loaded images?
314
No plugins/frameworks has yet been discovered, which does not work with *WebP Express*.
@@ -319,13 +396,23 @@ The following lazy load plugins/frameworks has been tested and works with *WebP
319
- [BJ Lazy Load](https://da.wordpress.org/plugins/bj-lazy-load/)
320
- [Owl Carousel 2](https://owlcarousel2.github.io/OwlCarousel2/)
321
322
### When is feature X coming? / Roadmap
323
No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
324
325
- Here are my current plans ahead: The 0.11 release will probably add a some diagnose tool – this should release some time spend in the forum. 0.12 could be focused on PNG. 0.13 might be displaying rules for NGINX. 0.14 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.15 might be multisite support. 0.16 might be a file manager-like interface for inspecting generated webp files. 0.17 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
326
327
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
328
329
330
## Changes in 0.9.0
331
- Optionally make .htaccess redirect directly to existing webp (improves performance)
1
# WebP Express
2
3
+ Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP.
4
5
The plugin is available on the Wordpress codex ([here](https://wordpress.org/plugins/webp-express/)), and developed on github ([here](https://github.com/rosell-dk/webp-express/)).
6
7
+ Note that to use the version on github, you need to *cd* into the plugin folder and run `composer update`.
8
+
9
## Description
10
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
11
12
+ ### The image converter
13
+ The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `cwebp`, `gd`, `imagick`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
14
+
15
+ ### The "Serving webp to browsers that supports it" part.
16
+
17
+ The plugin supports different ways of delivering webps to browsers that supports it:
18
+
19
+ 1. By routing jpeg/png images to the corresponding webp - or to the image converter if the image hasn't been converted yet.
20
+ 2. By altering the HTML, replacing image tags with *picture* tags. Missing webps are auto generated upon visit.
21
+ 3. By altering the HTML, replacing image URLs so all points to webp. The replacements only being made for browsers that supports webp. Again, missing webps are auto generated upon visit.
22
+ 4. In combination with *Cache Enabler*, the same as above can be achieved, but with page caching.
23
+ 5. You can also deliver webp to *all* browsers and add the [webpjs](http://webpjs.appspot.com) javascript, which provides webp support for browsers that doesn't support webp natively. You currently have to add the javascript yourself, but I expect to add an option for it in the next release.
24
25
The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and its "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md)
26
27
+ ### Benefits
28
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
29
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
30
- Better ranking in Google searches (performance is taken into account by Google)
31
- Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
32
+ - Currently ~73% of all traffic, and ~79% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
33
34
35
## Installation
42
### Configuring
43
You configure the plugin in *Settings > WebP Express*.
44
45
+ #### Operation modes
46
+ As sort of a main switch, you can choose between the following modes of operation:
47
+
48
+ *Varied image responses*:
49
+ WebP Express creates redirection rules for images, such that a request for a jpeg will result in a webp – but only if the request comes from a webp-enabled browser. If a webp already exists, it is served immediately. Otherwise it is converted and then served. Note that not all CDN's handles varied responses well.
50
+
51
+ *CDN friendly*:
52
+ In "CDN friendly" mode, a jpeg is always served as a jpeg. Instead of varying the image response, WebP Express alters the HTML for webp usage.
53
+
54
+ *Just redirect*:
55
+ In "just redirect" mode, WebP Express is used just for redirecting jpeg and pngs to existing webp images in the same folder. So in this mode, WebP express will not do any converting. It may be that you use another plugin for that, or that you converted the images off-line and uploaded them manually.
56
+
57
+ *Tweaked*:
58
+ Here you have all options available.
59
+
60
+
61
#### Conversion methods
62
WebP Express has a bunch of methods available for converting images: Executing cwebp binary, Gd extension, Imagick extension, ewww cloud converter and remote WebP express. Each requires *something*. In many cases, one of the conversion methods will be available. You can quickly identify which converters are working - there is a green icon next to them. Hovering conversion methods that are not working will show you what is wrong.
63
80
81
If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching. It could also be that your images are handled by nginx.
82
83
+ Note that the plugin does not change any HTML (unless you enabled the *Alter HTML* option). In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
84
85
- Open the page in Google Chrome
86
- Right-click the page and choose "Inspect"
95
### Notes
96
97
*Note:*
98
+ The redirect rules created in *.htaccess* are pointing to a PHP script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
99
100
*Note:*
101
Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
137
I shall write more on this FAQ item... Stay tuned.
138
139
### How can a webp image be served on an URL ending with "jpg"?
140
+ Easy enough. Browsers looks at the *content type* header rather than the URL to determine what it is that it gets. So, although it can be confusing that the resource at *example.com/image.jpg* is a webp image, rest assured that the browsers are not confused. To determine if the plugin is working, you must therefore examine the *content type* response header rather than the URL. See the "How do I verify that the plugin is working?" Faq item.
141
142
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
143
144
### I am on NGINX / OpenResty
145
+ It is possible to make WebP Express work on NGINX, but it requires manually inserting redirection rules in the NGINX configuration file (nginx.conf or the configuration file for the site, found in `/etc/nginx/sites-available`). On most setups the following rules should work:
146
147
```
148
if ($http_accept ~* "webp"){
187
188
But there is a case for using WebP Express rather than Jetpacks "Speed up image load times" feature:
189
190
+ Jetpack has the same drawback as the *Varied image responses* operation mode: If a user downloads the file, there will be a mismatch between the file extension and the image type (the file is ie called "logo.jpg", but it is really a webp image). I don't think that is a big issue, but for those who do, WebP Express might still be for you, even though you have Jetpack. And that is because WebP Express can be set up just to generate webp's, without doing the internal redirection to webp (will be possible from version 0.10.0). You can then for example use the [Cache Enabler](https://wordpress.org/plugins/cache-enabler/) plugin, which is able to generate and cache two versions of each page. One for browsers that accepts webp and one for those that don't. In the HTML for webp-enabled browsers, the images points directly to the webp files.
191
192
Pro Jetpack:
193
- It is a free CDN which serves webp out of the box.
212
213
Note: If you experience that the general auto option doesn't show, even though the above-mentioned requirements should be in order, check out [this support-thread](https://wordpress.org/support/topic/still-no-auto-option/).
214
215
+ ### How do I configure my CDN ("Varied image responses" mode)?
216
+ In *Varied image responses* operation mode, the image responses *varies* depending on whether the browser supports webp or not (which browsers signals in the *Accept* header). Some CDN's support this out of the box, others requires some configuration and others doesn't support it at all.
217
+
218
+ For a CDN to cooperate, it needs to
219
+ 1) forward the *Accept* header and
220
+ 2) Honour the Vary:Accept response header.
221
+
222
+ You can also make it "work" on some CDN's by bypassing cache for images. But I rather suggest that you try out the *CDN friendly* mode (see next FAQ item)
223
+
224
+ #### Status of some CDN's
225
+
226
+ - *KeyCDN*: Does not support varied image responses. I have added a feature request [here](https://community.keycdn.com/t/support-vary-accept-header-for-conditional-webp/1864). You can give it a +1 if you like!
227
+ - *Cloudflare*: See the "I am on Cloudflare" item
228
+ - *Cloudfront*: Works, but needs to be configured to forward the accept header. Go to *Distribution settings*, find the *Behavior tab*, select the Behavior and click the Edit button. Choose *Whitelist* from *Forward Headers* and then add the "Accept" header to the whitelist.
229
+
230
+ I shall add more to the list. You are welcome to help out [here](https://wordpress.org/support/topic/which-cdns-works-in-standard-mode/).
231
+
232
+ ### How do I make it work with CDN? ("CDN friendly" mode)
233
+ In *CDN friendly* mode, there is no trickery with varied image responses, so no special attention is required *on the CDN*.
234
+
235
+ However, there are other pitfalls.
236
+
237
+ The thing is that, unless you have the whole site on a CDN, you are probably using a plugin that *alters the HTML* in order to point your static assets to the CDN. If you have enabled the "Alter HTML" in WebP Express, it means that you now have *two alterations* on the image URLs!
238
+
239
+ How will that play out?
240
241
+ Well, if *WebP Express* gets to alter the HTML *after* the image URLs have been altered to point to a CDN, we have trouble. WebP Express does not alter external images but the URLs are now external.
242
+
243
+ However, if *WebP Express* gets to alter the HTML *before* the other plugin, things will work fine.
244
+
245
+ So it is important that *WebP Express* gets there first.
246
+
247
+ *The good news is that WebP Express does get there first on all the plugins I have tested.*
248
+
249
+ But what can you do if it doesn't?
250
+
251
+ Firstly, you have an option in WebP Express to select between:
252
+ 1. Use content filtering hooks (the_content, the_excerpt, etc)
253
+ 2. The complete page (using output buffering)
254
+
255
+ The content filtering hooks gets to process the content before output buffering does. So in case output buffering isn't early enough for you, choose the content filtering hooks.
256
+
257
+ There is a risk that you CDN plugin also uses content filtering hooks. I haven't encountered any though. But if there is any out there that does, chances are that they get to process the content before WebP Express, because I have set the priority of these hooks quite high (10000). The reasoning behind this is to that we want to replace images that might be inserted using the same hook (for example, a theme might use *the_content* filter to insert the featured image). If you do encounter a plugin for changing URLs for CDN which uses the content filtering hooks, you are currently out of luck. Let me know, so I can fix that (ie. by making the priority configurable)
258
+
259
+ Here are a list of some plugins for CDN and when they process the HTML:
260
+
261
+ | Plugin | Method | Hook(s) | Priority
262
+ | ----------------- | ------------------ | ------------------------------------------------ | ---------------
263
+ | BunnyCDN | Output buffering | template_redirect | default (10)
264
+ | CDN enabler | Output buffering | template_redirect | default (10)
265
+ | Jetpack | content filtering | the_content, etc | the_content: 10
266
+ | W3 Total Cache | Output buffering | no hooks. Buffering is started before hooks |
267
+ | WP Fastest Cache | Output buffering | no hooks. Buffering is started before hooks |
268
+ | WP Super Cache | Output buffering | init | default (10)
269
+
270
+
271
+ With output buffering the plugin that starts the output buffering first gets to process the output last. So WebP Express starts as late as possible, which is on the `template_redirect` hook, with priority 10000 (higher means later). This is later than the `init` hook, which is again later than the `no hooks`.
272
273
274
### I am on Cloudflare
275
Without configuration, Cloudflare will not maintain separate caches for jpegs and webp; all browsers will get jpeg. To make Cloudflare cache not only by URL, but also by header, you need to use the [Custom Cache Key](https://support.cloudflare.com/hc/en-us/articles/115004290387) page rule, and add *Header content* to make separate caches depending on the *Accept* request header.
287
### WebP Express / ShortPixel setup
288
Here is a recipe for using WebP Express together with ShortPixel, such that WebP Express generates the webp's, and ShortPixel only is used to create `<picture>` tags, when it detects a webp image in the same folder as an original.
289
290
+ **There is really no need to do this anymore, because WebP Express is now capable of replacing img tags with picture tags (check out the Alter HTML option)**
291
292
You need:
293
1 x WebP Express
294
1 x ShortPixel
295
296
+ *1. Setup WebP Express*
297
+ If you do not want to use serve varied images:
298
- Open WebP Express options
299
+ - Switch to *CDN friendly* mode.
300
- Set *File extension* to "Set to .webp"
301
+ - Make sure the *Convert non-existing webp-files upon request to original image* option is enabled
302
303
If you want to *ShortPixel* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
304
- Open WebP Express options
305
+ - Switch to *Varied image responses* mode.
306
- Set *Destination folder* to "Mingled"
307
- Set *File extension* to "Set to .webp"
308
309
+ *2. Setup ShortPixel*
310
- Install [ShortPixel](https://wordpress.org/plugins/shortpixel-image-optimiser/) the usual way
311
- Get an API key and enter it on the options page.
312
- In *Advanced*, enable the following options:
317
- *Automatically optimize images added by users in front end.*
318
- *Automatically optimize Media Library items after they are uploaded (recommended).*
319
320
+ *3. Visit a page*
321
As there are presumably no webps generated yet, ShortPixel will not generate `<picture>` tags on the first visit. However, the images that are referenced causes the WebP Express *Auto convert* feature to kick in and generate webp images for each image on that page.
322
323
+ *4. Visit the page again*
324
As *WebP Express* have generated webps in the same folder as the originals, *ShortPixel* detects these, and you should see `<picture>` tags which references the webp's.
325
326
+ *ShortPixel or Cache Enabler ?*
327
Cache Enabler has the advantage over ShortPixel that the HTML structure remains the same. With ShortPixel, image tags are wrapped in a `<picture>` tag structure, and by doing that, there is a risk of breaking styles.
328
329
Further, Cache Enabler *caches* the HTML. This is good for performance. However, this also locks you to using that plugin for caching. With ShortPixel, you can keep using your favourite caching plugin.
331
Cache Enabler will not work if you are caching HTML on a CDN, because the HTML varies depending on the *Accept* header and it doesn't signal this with a Vary:Accept header. You could however add that manually. ShortPixel does not have that issue, as the HTML is the same for all.
332
333
### WebP Express / Cache Enabler setup
334
+ The WebP Express / Cache Enabler setup is quite potent and very CDN-friendly. *Cache Enabler* is used for generating *and caching* two versions of the HTML (one for webp-enabled browsers and one for webp-disabled browsers)
335
336
The reason for doing this could be:
337
+ 1. You are using a CDN which cannot be configured to work in the "Varied image responses" mode.
338
+ 2. You could tweak your CDN to work in the "Varied image responses" mode, but you would have to do it by using the entire Accept header as key. Doing that would increase the risk of cache MISS, and you therefore decided that do not want to do that.
339
+ 3. You think it is problematic that when a user saves an image, it has the jpg extension, even though it is a webp image.
340
341
You need:
342
1 x WebP Express
343
1 x Cache Enabler
344
345
+ *1. Setup WebP Express*
346
+ If you do not want to use serve varied images:
347
- Open WebP Express options
348
+ - Switch to *CDN friendly* mode.
349
- Set *File extension* to "Set to .webp"
350
+ - Enable *Alter HTML* and select *Replace image URLs*. It is not absolutely neccessary, as Cache Enabler also alters HTML - but there are several reasons to do it. Firstly, *Cache Enabler* doesn't get as many URLs replaced as we do. WebP Express for example also replaces background urls in inline styles. Secondly, *Cache enabler* has [problems in edge cases](https://regexr.com/46isf). Thirdly, WebP Express can be configured to alter HTML to point to corresponding webp images, *before they even exists* which can be used in conjunction with the the *Convert non-existing webp-files upon request* option. And this is smart, because then you don't have trouble with *Cache Enabler* caching HTML which references the original images due to that some images hasn't been converted yet.
351
+ - If you enabled *Alter HTML*, also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*
352
+ - If you did not enable *Alter HTML*, enable *Convert non-existing webp-files upon request to original image*
353
354
If you want to *Cache Enabler* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
355
- Open WebP Express options
356
+ - Switch to *Varied image responses* mode.
357
- Set *Destination folder* to "Mingled"
358
- Set *File extension* to "Set to .webp"
359
+ - I suggest you enable *Alter HTML* and select *Replace image URLs*. And also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*.
360
361
+ *2. Setup Cache Enabler*
362
- Open the options
363
- Enable of the *Create an additional cached version for WebP image support* option
364
365
+ *3. If you did not enable Alter HTML and Reference webps that hasn't been converted yet: Let rise in a warm place until doubled*
366
+ *WebP Express* creates *webp* images on need basis. It needs page visits in order to do the conversions . Bulk conversion is on the roadmap, but until then, you need to visit all pages of relevance. You can either do it manually, let your visitors do it (that is: wait a bit), or, if you are on linux, you can use `wget` to grab your website:
367
368
```
369
wget -e robots=off -r -np -w 2 http://www.example.com
374
`-np` (no-parent) makes wget stay within the boundaries (doesn't go into parent folders)
375
`w 2` Waits two seconds between each request, in order not to stress the server
376
377
+ *4. Clear the Cache Enabler cache.*
378
Click the "Clear Cache" button in the top right corner in order to clear the Cache Enabler cache.
379
380
+ *5. Inspect the HTML*
381
When visiting a page with images on, different HTML will be served to browsers, depending on whether they support webp or not.
382
383
In a webp-enabled browser, the HTML may look like this: `<img src="image.webp">`, while in a non-webp enabled browser, it looks like this: `<img src="image.jpg">`
384
385
386
+ *6. Optionally add Cache Enabler rewrite rules in your .htaccess*
387
*Cache Enabler* provides some rewrite rules that redirects to the cached file directly in the `.htaccess`, bypassing PHP entirely. Their plugin doesn't do that for you, so you will have to do it manually in order to get the best performance. The rules are in the "Advanced configuration" section on [this page](https://www.keycdn.com/support/wordpress-cache-enabler-plugin).
388
389
390
### Does it work with lazy loaded images?
391
No plugins/frameworks has yet been discovered, which does not work with *WebP Express*.
396
- [BJ Lazy Load](https://da.wordpress.org/plugins/bj-lazy-load/)
397
- [Owl Carousel 2](https://owlcarousel2.github.io/OwlCarousel2/)
398
399
+ I have only tested the above in *Varied image responses* mode, but it should also work in *CDN friendly* mode. Both *Alter HTML* options have been designed to work with standard lazy load attributes.
400
+
401
### When is feature X coming? / Roadmap
402
No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
403
404
+ Here are my current plans ahead: The 0.12 release will allow webp for all browsers! - using [this wonderful javascript library](https://webpjs.appspot.com/). The 0.13 release will probably be multisite support, as this has been requested by many. 0.14 might be adding some diagnose tool – this should release some time spend in the forum. 0.54 could be focused on PNG. 0.16 might be displaying rules for NGINX. 0.17 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.18 might be a file manager-like interface for inspecting generated webp files. 0.19 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
405
406
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
407
408
+ ## Changes in 0.11.0
409
+ - Alter HTML to point to webp files (choose between picture tags or simply altering all image urls)
410
+ - Convert non-existing webp-files upon request (means you can reference the converted webp files before they are actually converted!)
411
+
412
+ ## Changes in 0.10.0
413
+ - Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
414
+ - WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
415
+ - Cache-Control header is now added in *.htaccess*, when redirecting directly to existing webp
416
417
## Changes in 0.9.0
418
- Optionally make .htaccess redirect directly to existing webp (improves performance)
README.txt CHANGED
@@ -4,27 +4,38 @@ Donate link: https://ko-fi.com/rosell
4
Tags: webp, images, performance
5
Requires at least: 4.0
6
Tested up to: 5.0
7
- Stable tag: 0.10.0
8
Requires PHP: 5.6
9
License: GPLv3
10
License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
12
- Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on all images (media library, galleries, theme images, ...).
13
14
== Description ==
15
16
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
17
18
- The plugin basically routes jpeg/png images to an image converter, or - if the image converter has already converted the image - directly to a converted image. The approach has the benefit that is works regardless of how an image found its way into your server - be it Media Library, Galleries, or even theme images referenced with CSS.
19
20
- The plugin is developed on [github](https://github.com/rosell-dk/webp-express/). It builds upon the [WebPConvert](https://github.com/rosell-dk/webp-convert) library and the "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md)
21
22
- #### Benefits
23
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
24
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
25
- Better ranking in Google searches (performance is taken into account by Google)
26
- - Less bandwidth consumption - makes a difference when abroad and in the parts of the world with slow and expensive internet connections.
27
- - Currently ~73% of all traffic, and ~78% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
28
29
== Installation ==
30
@@ -36,6 +47,21 @@ The plugin is developed on [github](https://github.com/rosell-dk/webp-express/).
36
### Configuring
37
You configure the plugin in *Settings > WebP Express*.
38
39
#### Conversion methods
40
WebP Express has a bunch of methods available for converting images: Executing cwebp binary, Gd extension, Imagick extension, ewww cloud converter and remote WebP express. Each requires *something*. In many cases, one of the conversion methods will be available. You can quickly identify which converters are working - there is a green icon next to them. Hovering conversion methods that are not working will show you what is wrong.
41
@@ -56,7 +82,7 @@ If you do not the "auto" option available:
56
### Notes
57
58
*Note:*
59
- The redirect rules created in *.htaccess* are pointing to a PHP script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
60
61
*Note:*
62
Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
@@ -83,7 +109,7 @@ Once, you have a converter, that works, when you click the "test"-button, you ar
83
84
If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching. It could also be that your images are handled by nginx.
85
86
- Note that the plugin does not change any HTML. In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
87
88
- Open the page in Google Chrome
89
- Right-click the page and choose "Inspect"
@@ -119,12 +145,12 @@ I shall write more on this FAQ item... Stay tuned.
119
120
121
= How can a webp image be served on an URL ending with "jpg"? =
122
- Easy enough. Browsers looks at the *content type* header rather than the URL to determine what it is that it gets. So, although it can be confusing that the resource at *example.com/image.jpg* is a webp image, rest asure that the browsers are not confused. To determine if the plugin is working, you must therefore examine the *content type* response header rather than the URL. See the "How do I verify that the plugin is working?" Faq item.
123
124
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
125
126
= I am on NGINX / OpenResty =
127
- It is possible to make WebP Express work on NGINX, but it requieres manually inserting redirection rules in the NGINX configuration file (nginx.conf or the configuration file for the site, found in `/etc/nginx/sites-available`). On most setups the following rules should work:
128
129
```
130
if ($http_accept ~* "webp"){
@@ -194,35 +220,62 @@ If you have the *Imagick*, the *Imagick binary* or the *Remote WebP Express* con
194
195
Note: If you experience that the general auto option doesn't show, even though the above-mentioned requirements should be in order, check out [this support-thread](https://wordpress.org/support/topic/still-no-auto-option/).
196
197
- = How do I configure my CDN? (Standard mode) =
198
- In *Standard* operation mode, the response *varies* depending on whether the browser supports webp or not (which browsers signals in the *Accept* header). Some CDN's support this out of the box, others requires some configuration and others doesn't support it at all.
199
200
For a CDN to cooperate, it needs to
201
1) forward the *Accept* header and
202
2) Honour the Vary:Accept response header.
203
204
- You can also make it "work" on some CDN's by bypassing cache for images. But I rather suggest that you try out the *Just convert* mode (see next FAQ item)
205
206
*Status of some CDN's*:
207
208
- - *KeyCDN*: Does not support varied responses. I have added a feature request [here](https://community.keycdn.com/t/support-vary-accept-header-for-conditional-webp/1864). You can give it a +1 if you like!
209
- *Cloudflare*: See the "I am on Cloudflare" item
210
- *Cloudfront*: Works, but needs to be configured to forward the accept header. Go to *Distribution settings*, find the *Behavior tab*, select the Behavior and click the Edit button. Choose *Whitelist* from *Forward Headers* and then add the "Accept" header to the whitelist.
211
212
I shall add more to the list. You are welcome to help out [here](https://wordpress.org/support/topic/which-cdns-works-in-standard-mode/).
213
214
- = How do I configure my CDN? (Just convert mode) =
215
- In *Just convert* mode, there is no trickery with varied responses, so no special attention is required *on the CDN*.
216
217
- In *Just-convert* mode, to get things going, you must install a plugin which alters the HTML. Before continuing, read the *WebP Express / ShortPixel setup* and the *WebP Express / Cache Enabler setup* items.
218
219
- If you have set up your whole site to be on CDN, things should now work.
220
221
- If however you only want the static assets to be on the CDN, you need a plugin to alter the HTML for that (ie w3tc). So now we have two alterations!
222
223
- But this should work, as long the alterations that creates `<picture>` tags happens *before* the alterations that points the images to the CDN.
224
225
- Tip: In *ShortPixel*, you can select if HTML alterations should happen in hooks or in output buffer. Hooks are executed before they reaches the output buffer.
226
227
= I am on Cloudflare =
228
Without configuration, Cloudflare will not maintain separate caches for jpegs and webp; all browsers will get jpeg. To make Cloudflare cache not only by URL, but also by header, you need to use the [Custom Cache Key](https://support.cloudflare.com/hc/en-us/articles/115004290387) page rule, and add *Header content* to make separate caches depending on the *Accept* request header.
@@ -240,24 +293,22 @@ To make *WebP Express* work on a free Cloudflare account, you have the following
240
### WebP Express / ShortPixel setup
241
Here is a recipe for using WebP Express together with ShortPixel, such that WebP Express generates the webp's, and ShortPixel only is used to create `<picture>` tags, when it detects a webp image in the same folder as an original.
242
243
- The reason for doing this could be:
244
- 1. You are using a CDN which cannot be configured to work with the Standard WebP Express setup.
245
- 2. You think it is problematic that when a user saves an image, it has the jpg extension, even though it is a webp image.
246
247
You need:
248
1 x WebP Express
249
1 x ShortPixel
250
251
*1. Setup WebP Express*
252
- If you are using a CDN which cannot be configured to work in *Standard mode*:
253
- Open WebP Express options
254
- - Switch to *Just convert* mode.
255
- Set *File extension* to "Set to .webp"
256
- - Make sure the *Auto convert* option is enabled
257
258
If you want to *ShortPixel* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
259
- Open WebP Express options
260
- - Switch to *Standard* mode.
261
- Set *Destination folder* to "Mingled"
262
- Set *File extension* to "Set to .webp"
263
@@ -286,36 +337,39 @@ Further, Cache Enabler *caches* the HTML. This is good for performance. However,
286
Cache Enabler will not work if you are caching HTML on a CDN, because the HTML varies depending on the *Accept* header and it doesn't signal this with a Vary:Accept header. You could however add that manually. ShortPixel does not have that issue, as the HTML is the same for all.
287
288
### WebP Express / Cache Enabler setup
289
- The WebP Express / Cache Enabler setup is quite potent and very CDN-friendly. Cache Enabler is used for generating and caching two versions of the HTML (one for webp-enabled browsers and one for webp-disabled browsers)
290
291
The reason for doing this could be:
292
- 1. You are using a CDN which cannot be configured to work with the Standard WebP Express setup.
293
- 2. You think it is problematic that when a user saves an image, it has the jpg extension, even though it is a webp image.
294
295
You need:
296
1 x WebP Express
297
1 x Cache Enabler
298
299
*1. Setup WebP Express*
300
- If you are using a CDN which cannot be configured to work in *Standard mode*:
301
- Open WebP Express options
302
- - Switch to *Just convert* mode.
303
- Set *File extension* to "Set to .webp"
304
- - Make sure the *Auto convert* option is enabled
305
306
If you want to *Cache Enabler* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
307
- Open WebP Express options
308
- - Switch to *Standard* mode.
309
- Set *Destination folder* to "Mingled"
310
- Set *File extension* to "Set to .webp"
311
312
*2. Setup Cache Enabler*
313
- Open the options
314
- Enable of the *Create an additional cached version for WebP image support* option
315
316
-
317
- *3. Let rise in a warm place until doubled*
318
- *WebP Express* creates *webp* images on need basis. It needs page visits in order to do the convertions . Bulk conversion is on the roadmap, but until then, you need to visit all pages of relevance. You can either do it manually, let your visitors do it (that is: wait a bit), or, if you are on linux, you can use `wget` to grab your website:
319
320
```
321
wget -e robots=off -r -np -w 2 http://www.example.com
@@ -338,15 +392,8 @@ In a webp-enabled browser, the HTML may look like this: `<img src="image.webp">`
338
*6. Optionally add Cache Enabler rewrite rules in your .htaccess*
339
*Cache Enabler* provides some rewrite rules that redirects to the cached file directly in the `.htaccess`, bypassing PHP entirely. Their plugin doesn't do that for you, so you will have to do it manually in order to get the best performance. The rules are in the "Advanced configuration" section on [this page](https://www.keycdn.com/support/wordpress-cache-enabler-plugin).
340
341
- *ShortPixel or Cache Enabler ?*
342
- Cache Enabler has the advantage over ShortPixel that the HTML structure remains the same. With ShortPixel, image tags are wrapped in a `<picture>` tag structure, and by doing that, there is a risk of breaking styles.
343
-
344
- Further, Cache Enabler *caches* the HTML. This is good for performance. However, this also locks you to using that plugin for caching. With ShortPixel, you can keep using your favourite caching plugin.
345
-
346
- Cache Enabler will not work if you are caching HTML on a CDN, because the HTML varies depending on the *Accept* header and it doesn't signal this with a Vary:Accept header. You could however add that manually. ShortPixel does not have that issue, as the HTML is the same for all.
347
-
348
= Does it work with lazy loaded images? =
349
- No plugins/frameworks has yet been discovered, which does not work with *WebP Express* in standard mode.
350
351
The most common way of lazy-loading is by setting a *data-src* attribute on the image and let javascript use that value for setten the *src* attribute. That method works, as the image request, seen from the server side, is indistinguishable from any other image request. It could however be that some obscure lazy load implementation would load the image with an XHR request. In that case, the *Accept* header will not contain 'image/webp', but '*/*', and a jpeg will be served, even though the browser supports webp.
352
@@ -354,10 +401,12 @@ The following lazy load plugins/frameworks has been tested and works with *WebP
354
- [BJ Lazy Load](https://da.wordpress.org/plugins/bj-lazy-load/)
355
- [Owl Carousel 2](https://owlcarousel2.github.io/OwlCarousel2/)
356
357
= When is feature X coming? / Roadmap =
358
No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
359
360
- Here are my current plans ahead: The 0.11 release will add ability to alter HTML (both picture tag syntax, as ShortPixel does and replace image URLs as Cache Enabler does). The 0.12 release will allow webp for all browsers! - using [this wonderful javascript library](https://webpjs.appspot.com/). The 0.13 release will probably add a some diagnose tool – this should release some time spend in the forum. 0.14 could be focused on PNG. 0.15 might be displaying rules for NGINX. 0.16 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.17 might be multisite support. 0.18 might be a file manager-like interface for inspecting generated webp files. 0.19 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
361
362
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
363
@@ -370,6 +419,12 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
370
371
== Changelog ==
372
373
= 0.10.0 =
374
* Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
375
* WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
@@ -457,6 +512,9 @@ For older releases, check out changelog.txt
457
458
== Upgrade Notice ==
459
460
= 0.10.0 =
461
WebP Express can now be used in conjunction with Cache Enabler and ShortPixel. Also introduced "Operation modes" in order to keep setting screens simple but still allow tweaking.
462
4
Tags: webp, images, performance
5
Requires at least: 4.0
6
Tested up to: 5.0
7
+ Stable tag: 0.11.0
8
Requires PHP: 5.6
9
License: GPLv3
10
License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
12
+ Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP.
13
14
== Description ==
15
16
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
17
18
+ ### The image converter
19
+ The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `cwebp`, `gd`, `imagick`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
20
21
+ ### The "Serving webp to browsers that supports it" part.
22
23
+ The plugin supports different ways of delivering webps to browsers that supports it:
24
+
25
+ 1. By routing jpeg/png images to the corresponding webp - or to the image converter if the image hasn't been converted yet.
26
+ 2. By altering the HTML, replacing image tags with *picture* tags. Missing webps are auto generated upon visit.
27
+ 3. By altering the HTML, replacing image URLs so all points to webp. The replacements only being made for browsers that supports webp. Again, missing webps are auto generated upon visit.
28
+ 4. In combination with *Cache Enabler*, the same as above can be achieved, but with page caching.
29
+ 5. You can also deliver webp to *all* browsers and add the [webpjs](http://webpjs.appspot.com) javascript, which provides webp support for browsers that doesn't support webp natively. You currently have to add the javascript yourself, but I expect to add an option for it in the next release.
30
+
31
+ The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and its "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md)
32
+
33
+ ### Benefits
34
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
35
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
36
- Better ranking in Google searches (performance is taken into account by Google)
37
+ - Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
38
+ - Currently ~73% of all traffic, and ~79% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
39
40
== Installation ==
41
47
### Configuring
48
You configure the plugin in *Settings > WebP Express*.
49
50
+ #### Operation modes
51
+ As sort of a main switch, you can choose between the following modes of operation:
52
+
53
+ *Varied image responses*:
54
+ WebP Express creates redirection rules for images, such that a request for a jpeg will result in a webp – but only if the request comes from a webp-enabled browser. If a webp already exists, it is served immediately. Otherwise it is converted and then served. Note that not all CDN's handles varied responses well.
55
+
56
+ *CDN friendly*:
57
+ In "CDN friendly" mode, a jpeg is always served as a jpeg. Instead of varying the image response, WebP Express alters the HTML for webp usage.
58
+
59
+ *Just redirect*:
60
+ In "just redirect" mode, WebP Express is used just for redirecting jpeg and pngs to existing webp images in the same folder. So in this mode, WebP express will not do any converting. It may be that you use another plugin for that, or that you converted the images off-line and uploaded them manually.
61
+
62
+ *Tweaked*:
63
+ Here you have all options available.
64
+
65
#### Conversion methods
66
WebP Express has a bunch of methods available for converting images: Executing cwebp binary, Gd extension, Imagick extension, ewww cloud converter and remote WebP express. Each requires *something*. In many cases, one of the conversion methods will be available. You can quickly identify which converters are working - there is a green icon next to them. Hovering conversion methods that are not working will show you what is wrong.
67
82
### Notes
83
84
*Note:*
85
+ The redirect rules created in *.htaccess* are pointing to a PHP script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
86
87
*Note:*
88
Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
109
110
If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching. It could also be that your images are handled by nginx.
111
112
+ Note that the plugin does not change any HTML (unless you enabled the *Alter HTML* option). In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
113
114
- Open the page in Google Chrome
115
- Right-click the page and choose "Inspect"
145
146
147
= How can a webp image be served on an URL ending with "jpg"? =
148
+ Easy enough. Browsers looks at the *content type* header rather than the URL to determine what it is that it gets. So, although it can be confusing that the resource at *example.com/image.jpg* is a webp image, rest assured that the browsers are not confused. To determine if the plugin is working, you must therefore examine the *content type* response header rather than the URL. See the "How do I verify that the plugin is working?" Faq item.
149
150
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
151
152
= I am on NGINX / OpenResty =
153
+ It is possible to make WebP Express work on NGINX, but it requires manually inserting redirection rules in the NGINX configuration file (nginx.conf or the configuration file for the site, found in `/etc/nginx/sites-available`). On most setups the following rules should work:
154
155
```
156
if ($http_accept ~* "webp"){
220
221
Note: If you experience that the general auto option doesn't show, even though the above-mentioned requirements should be in order, check out [this support-thread](https://wordpress.org/support/topic/still-no-auto-option/).
222
223
+ = How do I configure my CDN in "Varied image responses" operation mode? =
224
+ In *Varied image responses* operation mode, the image responses *varies* depending on whether the browser supports webp or not (which browsers signals in the *Accept* header). Some CDN's support this out of the box, others requires some configuration and others doesn't support it at all.
225
226
For a CDN to cooperate, it needs to
227
1) forward the *Accept* header and
228
2) Honour the Vary:Accept response header.
229
230
+ You can also make it "work" on some CDN's by bypassing cache for images. But I rather suggest that you try out the *CDN friendly* mode (see next FAQ item)
231
232
*Status of some CDN's*:
233
234
+ - *KeyCDN*: Does not support varied image responses. I have added a feature request [here](https://community.keycdn.com/t/support-vary-accept-header-for-conditional-webp/1864). You can give it a +1 if you like!
235
- *Cloudflare*: See the "I am on Cloudflare" item
236
- *Cloudfront*: Works, but needs to be configured to forward the accept header. Go to *Distribution settings*, find the *Behavior tab*, select the Behavior and click the Edit button. Choose *Whitelist* from *Forward Headers* and then add the "Accept" header to the whitelist.
237
238
I shall add more to the list. You are welcome to help out [here](https://wordpress.org/support/topic/which-cdns-works-in-standard-mode/).
239
240
+ ### How do I make it work with CDN? ("CDN friendly" mode)
241
+ In *CDN friendly* mode, there is no trickery with varied image responses, so no special attention is required *on the CDN*.
242
+
243
+ However, there are other pitfalls.
244
+
245
+ The thing is that, unless you have the whole site on a CDN, you are probably using a plugin that *alters the HTML* in order to point your static assets to the CDN. If you have enabled the "Alter HTML" in WebP Express, it means that you now have *two alterations* on the image URLs!
246
+
247
+ How will that play out?
248
+
249
+ Well, if *WebP Express* gets to alter the HTML *after* the image URLs have been altered to point to a CDN, we have trouble. WebP Express does not alter external images but the URLs are now external.
250
+
251
+ However, if *WebP Express* gets to alter the HTML *before* the other plugin, things will work fine.
252
+
253
+ So it is important that *WebP Express* gets there first.
254
+
255
+ *The good news is that WebP Express does get there first on all the plugins I have tested.*
256
257
+ But what can you do if it doesn't?
258
259
+ Firstly, you have an option in WebP Express to select between:
260
+ 1. Use content filtering hooks (the_content, the_excerpt, etc)
261
+ 2. The complete page (using output buffering)
262
263
+ The content filtering hooks gets to process the content before output buffering does. So in case output buffering isn't early enough for you, choose the content filtering hooks.
264
265
+ There is a risk that you CDN plugin also uses content filtering hooks. I haven't encountered any though. But if there is any out there that does, chances are that they get to process the content before WebP Express, because I have set the priority of these hooks quite high (10000). The reasoning behind this is to that we want to replace images that might be inserted using the same hook (for example, a theme might use *the_content* filter to insert the featured image). If you do encounter a plugin for changing URLs for CDN which uses the content filtering hooks, you are currently out of luck. Let me know, so I can fix that (ie. by making the priority configurable)
266
267
+ Here are a list of some plugins for CDN and when they process the HTML:
268
+
269
+ | Plugin | Method | Hook(s) | Priority
270
+ | ----------------- | ------------------ | ------------------------------------------------ | ---------------
271
+ | BunnyCDN | Output buffering | template_redirect | default (10)
272
+ | CDN enabler | Output buffering | template_redirect | default (10)
273
+ | Jetpack | content filtering | the_content, etc | the_content: 10
274
+ | W3 Total Cache | Output buffering | no hooks. Buffering is started before hooks |
275
+ | WP Fastest Cache | Output buffering | no hooks. Buffering is started before hooks |
276
+ | WP Super Cache | Output buffering | init | default (10)
277
+
278
+ With output buffering the plugin that starts the output buffering first gets to process the output last. So WebP Express starts as late as possible, which is on the `template_redirect` hook, with priority 10000 (higher means later). This is later than the `init` hook, which is again later than the `no hooks`.
279
280
= I am on Cloudflare =
281
Without configuration, Cloudflare will not maintain separate caches for jpegs and webp; all browsers will get jpeg. To make Cloudflare cache not only by URL, but also by header, you need to use the [Custom Cache Key](https://support.cloudflare.com/hc/en-us/articles/115004290387) page rule, and add *Header content* to make separate caches depending on the *Accept* request header.
293
### WebP Express / ShortPixel setup
294
Here is a recipe for using WebP Express together with ShortPixel, such that WebP Express generates the webp's, and ShortPixel only is used to create `<picture>` tags, when it detects a webp image in the same folder as an original.
295
296
+ **There is really no need to do this anymore, because WebP Express is now capable of replacing img tags with picture tags (check out the Alter HTML option)**
297
298
You need:
299
1 x WebP Express
300
1 x ShortPixel
301
302
*1. Setup WebP Express*
303
+ If you do not want to use serve varied images:
304
- Open WebP Express options
305
+ - Switch to *CDN friendly* mode.
306
- Set *File extension* to "Set to .webp"
307
+ - Make sure the *Convert non-existing webp-files upon request to original image* option is enabled
308
309
If you want to *ShortPixel* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
310
- Open WebP Express options
311
+ - Switch to *Varied image responses* mode.
312
- Set *Destination folder* to "Mingled"
313
- Set *File extension* to "Set to .webp"
314
337
Cache Enabler will not work if you are caching HTML on a CDN, because the HTML varies depending on the *Accept* header and it doesn't signal this with a Vary:Accept header. You could however add that manually. ShortPixel does not have that issue, as the HTML is the same for all.
338
339
### WebP Express / Cache Enabler setup
340
+ The WebP Express / Cache Enabler setup is quite potent and very CDN-friendly. *Cache Enabler* is used for generating *and caching* two versions of the HTML (one for webp-enabled browsers and one for webp-disabled browsers)
341
342
The reason for doing this could be:
343
+ 1. You are using a CDN which cannot be configured to work in the "Varied image responses" mode.
344
+ 2. You could tweak your CDN to work in the "Varied image responses" mode, but you would have to do it by using the entire Accept header as key. Doing that would increase the risk of cache MISS, and you therefore decided that do not want to do that.
345
+ 3. You think it is problematic that when a user saves an image, it has the jpg extension, even though it is a webp image.
346
347
You need:
348
1 x WebP Express
349
1 x Cache Enabler
350
351
*1. Setup WebP Express*
352
+ If you do not want to use serve varied images:
353
- Open WebP Express options
354
+ - Switch to *CDN friendly* mode.
355
- Set *File extension* to "Set to .webp"
356
+ - Enable *Alter HTML* and select *Replace image URLs*. It is not absolutely neccessary, as Cache Enabler also alters HTML - but there are several reasons to do it. Firstly, *Cache Enabler* doesn't get as many URLs replaced as we do. WebP Express for example also replaces background urls in inline styles. Secondly, *Cache enabler* has [problems in edge cases](https://regexr.com/46isf). Thirdly, WebP Express can be configured to alter HTML to point to corresponding webp images, *before they even exists* which can be used in conjunction with the the *Convert non-existing webp-files upon request* option. And this is smart, because then you don't have trouble with *Cache Enabler* caching HTML which references the original images due to that some images hasn't been converted yet.
357
+ - If you enabled *Alter HTML*, also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*
358
+ - If you did not enable *Alter HTML*, enable *Convert non-existing webp-files upon request to original image*
359
360
If you want to *Cache Enabler* to create <picture> tags but still want the magic to work on other images (such as images are referenced from CSS or javascript):
361
- Open WebP Express options
362
+ - Switch to *Varied image responses* mode.
363
- Set *Destination folder* to "Mingled"
364
- Set *File extension* to "Set to .webp"
365
+ - I suggest you enable *Alter HTML* and select *Replace image URLs*. And also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*.
366
367
*2. Setup Cache Enabler*
368
- Open the options
369
- Enable of the *Create an additional cached version for WebP image support* option
370
371
+ *3. If you did not enable Alter HTML and Reference webps that hasn't been converted yet: Let rise in a warm place until doubled*
372
+ *WebP Express* creates *webp* images on need basis. It needs page visits in order to do the conversions . Bulk conversion is on the roadmap, but until then, you need to visit all pages of relevance. You can either do it manually, let your visitors do it (that is: wait a bit), or, if you are on linux, you can use `wget` to grab your website:
373
374
```
375
wget -e robots=off -r -np -w 2 http://www.example.com
392
*6. Optionally add Cache Enabler rewrite rules in your .htaccess*
393
*Cache Enabler* provides some rewrite rules that redirects to the cached file directly in the `.htaccess`, bypassing PHP entirely. Their plugin doesn't do that for you, so you will have to do it manually in order to get the best performance. The rules are in the "Advanced configuration" section on [this page](https://www.keycdn.com/support/wordpress-cache-enabler-plugin).
394
395
= Does it work with lazy loaded images? =
396
+ No plugins/frameworks has yet been discovered, which does not work with *WebP Express*.
397
398
The most common way of lazy-loading is by setting a *data-src* attribute on the image and let javascript use that value for setten the *src* attribute. That method works, as the image request, seen from the server side, is indistinguishable from any other image request. It could however be that some obscure lazy load implementation would load the image with an XHR request. In that case, the *Accept* header will not contain 'image/webp', but '*/*', and a jpeg will be served, even though the browser supports webp.
399
401
- [BJ Lazy Load](https://da.wordpress.org/plugins/bj-lazy-load/)
402
- [Owl Carousel 2](https://owlcarousel2.github.io/OwlCarousel2/)
403
404
+ I have only tested the above in *Varied image responses* mode, but it should also work in *CDN friendly* mode. Both *Alter HTML* options have been designed to work with standard lazy load attributes.
405
+
406
= When is feature X coming? / Roadmap =
407
No schedule. I move forward as time allows. I currently spend a lot of time answering questions in the support forum. If someone would be nice and help out answering questions here, it would allow me to spend that time developing. Also, donations would allow me to turn down some of the more boring requests from my customers, and speed things up here.
408
409
+ Here are my current plans ahead: The 0.12 release will allow webp for all browsers! - using [this wonderful javascript library](https://webpjs.appspot.com/). The 0.13 release will probably be multisite support, as this has been requested by many. 0.14 might be adding some diagnose tool – this should release some time spend in the forum. 0.54 could be focused on PNG. 0.16 might be displaying rules for NGINX. 0.17 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.18 might be a file manager-like interface for inspecting generated webp files. 0.19 might be WAMP support. The current milestones, their subtasks and their progress can be viewed here: https://github.com/rosell-dk/webp-express/milestones
410
411
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
412
419
420
== Changelog ==
421
422
+ = 0.11.0 =
423
+ * Alter HTML to point to webp files (choose between picture tags or simply altering all image urls)
424
+ * Convert non-existing webp-files upon request (means you can reference the converted webp files before they are actually converted!)
425
+
426
+ For more info, see the closed issues on the 0.11.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/14?closed=1
427
+
428
= 0.10.0 =
429
* Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
430
* WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
512
513
== Upgrade Notice ==
514
515
+ = 0.11.0 =
516
+ WebP Express can now alter HTML to either point to webp images directly or by using the picture tag syntax. Also, non-existing webp-files can be converted upon request (means you can reference the converted webp files before they are actually converted!)
517
+
518
= 0.10.0 =
519
WebP Express can now be used in conjunction with Cache Enabler and ShortPixel. Also introduced "Operation modes" in order to keep setting screens simple but still allow tweaking.
520
changelog.txt CHANGED
@@ -1,3 +1,9 @@
1
= 0.10.0 =
2
* Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
3
* WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
1
+ = 0.11.0 =
2
+ * Alter HTML to point to webp files (choose between picture tags or simply altering all image urls)
3
+ * Convert non-existing webp-files upon request (means you can reference the converted webp files before they are actually converted!)
4
+
5
+ For more info, see the closed issues on the 0.11.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/14?closed=1
6
+
7
= 0.10.0 =
8
* Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
9
* WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
composer.json CHANGED
@@ -1,5 +1,9 @@
1
{
2
- "minimum-stability": "dev",
3
"repositories": [
4
{
5
"type": "vcs",
@@ -12,6 +16,24 @@
12
],
13
"require": {
14
"rosell-dk/webp-convert": "dev-master",
15
- "rosell-dk/webp-convert-cloud-service": "dev-master"
16
- }
17
}
1
{
2
+ "name": "rosell-dk/webp-express",
3
+ "description": "WebP for the masses",
4
+ "type": "project",
5
+ "license": "MIT",
6
+ "minimum-stability": "stable",
7
"repositories": [
8
{
9
"type": "vcs",
16
],
17
"require": {
18
"rosell-dk/webp-convert": "dev-master",
19
+ "rosell-dk/webp-convert-cloud-service": "dev-master",
20
+ "rosell-dk/dom-util-for-webp": "^0.2.0"
21
+ },
22
+ "require-dev": {
23
+ "phpunit/phpunit": "^8.0@dev"
24
+ },
25
+ "scripts": {
26
+ "ci": [
27
+ "@test",
28
+ "@composer validate --no-check-all --strict"
29
+ ],
30
+ "test": "phpunit tests"
31
+ },
32
+ "authors": [
33
+ {
34
+ "name": "Bjørn Rosell",
35
+ "homepage": "https://www.bitwise-it.dk/contact",
36
+ "role": "Project Author"
37
+ }
38
+ ]
39
}
docs/regex.md ADDED
@@ -0,0 +1,154 @@
1
+ Pattern used for image urls:
2
+ in attributes: https://regexr.com/46jat
3
+ in css: https://regexr.com/46jcg
4
+
5
+ In case reqexr.com should be down, here are the content:
6
+
7
+
8
+ # in attributes
9
+
10
+ *pattern:*
11
+ (?<=(?:<(img|source|input|iframe)[^>]*\s+(src|srcset|data-[^=]*)\s*=\s*[\"\']?))(?:[^\"\'>]+)(\.png|\.jp[e]?g)(\s\d+w)?(?=\/?[\"\'\s\>])
12
+
13
+ *text:*
14
+ Notice: The pattern is meant for PHP and contains syntax which only works in some browsers. It works in Chrome. Not in Firefox.
15
+
16
+ The following should produce matches:
17
+ <img src="header.jpg">
18
+ <img src="/header.jpg">
19
+ <img src="http://example.com/header.jpeg" alt="">
20
+ <img src="http://example.com/header.jpg">
21
+ <img src="http://example.com/header.jpg"/>
22
+ <img src = "http://example.com/header.jpg">
23
+ <img src=http://example.com/header.jpg alt="">
24
+ <img src=http://example.com/header.jpg>
25
+ <img src=http://example.com/header.jpg alt="hello">
26
+ <img src=http://example.com/header.jpg />
27
+ <img src=http://example.com/header_.jpg/>
28
+ <picture><source src="http://example.com/header.jpg"><img src="http://example.com/header.jpg"></picture>
29
+ <input type="image" src="http://example.com/flamingo.jpg">
30
+ <iframe src="http://example.com/image.jpg"></iframe>
31
+
32
+
33
+
34
+ In srcset, the whole attribute must be matched
35
+ <img src="http://example.com/header.jpg" srcset="http://example.com/header.jpg 1000w">
36
+ <img src="http://example.com/header.jpg" srcset="http://example.com/header.jpg 1000w,http://example.com/header.jpg 1000w, http://example.com/header.jpg 2000w">
37
+ <img src="http://example.com/header.jpg" srcset="http://example.com/header-150x150.jpg 500w,http://example.com/header.jpg-300x300.jpg" sizes="(max-width: 480px) 100vw, (max-width: 900px) 33vw, 254px" alt="" width="100" height="100">
38
+
39
+ Common lazy load attributes are matched:
40
+ <img data-cvpsrc="http://example.com/header.jpg">
41
+ <img data-cvpset="http://example.com/header.jpg">
42
+ <img data-thumb="http://example.com/header.jpg">
43
+ <img data-bg-url="http://example.com/header.jpg">
44
+ <img data-large_image="http://example.com/header.jpg">
45
+ <img data-lazyload="http://example.com/header.jpg">
46
+ <img data-source-url="http://example.com/header.jpg">
47
+ <img data-srcsmall="http://example.com/header.jpg">
48
+ <img data-srclarge="http://example.com/header.jpg">
49
+ <img data-srcfull="http://example.com/header.jpg">
50
+ <img data-slide-img="http://example.com/header.jpg">
51
+ <img data-lazy-original="http://example.com/header.jpg">
52
+
53
+
54
+ The following should NOT produce matches:
55
+ -----------------------------------------
56
+
57
+ Ignore URLs with query string:
58
+ <img src="http://example.com/header.jpg?width=200">
59
+
60
+ <img src="http://example.com/tegning.jpg.webp" alt="">
61
+ <img src="http://example.com/tegning.jpglidilo" alt="">
62
+ <img src="http://example.com/header.jpg/hi-res">
63
+ <img src=http://example.com/header.gif alt=nice-jpg>
64
+ <img src="http://example.com/tegning.webp" alt="">
65
+ src="http://example.com/header.jpeg"
66
+ <article data-src="http://example.com/header.jpg" />
67
+ <img><script src="http://example.com/script.js?preload=image.jpg">
68
+
69
+
70
+ I use another pattern for matching image urls in styles: https://regexr.com/46jcg
71
+
72
+ It matches stuff like this:
73
+ <div style="background-image: url('http://example.com/image.png'), url("/image2.jpeg", url(http://example.com/image3.jpg);"></div>
74
+ <div style="background: url ("http://example.com/image2.jpg")"></div>
75
+ <style>#myphoto {background: url("http://example.com/image2.jpg")}</style>
76
+
77
+ I have another pattern where we allow QS here: https://regexr.com/46ivi
78
+
79
+ PS: The rules are used for the WebP Express plugin for Wordpress
80
+
81
+ PPS: This regex is used in WPFastestCache (not just images)
82
+ // $content = preg_replace_callback("/(srcset|src|href|data-cvpsrc|data-cvpset|data-thumb|data-bg-url|data-large_image|data-lazyload|data-source-url|data-srcsmall|data-srclarge|data-srcfull|data-slide-img|data-lazy-original)\s{0,2}\=[\'\"]([^\'\"]+)[\'\"]/i", array($this, 'cdn_replace_urls'), $content);
83
+
84
+ PPPS:
85
+ As we are limiting to a few tags (img, source, input, etc), and only match image urls ending with (png|jpe?g), I deem it ok to match in ANY "data-" attribute.
86
+ But if you want to limit it to attributes that smells like they are used for images you can do this:
87
+ (src|srcset|data-[^=]*(lazy|small|slide|img|large|src|thumb|source|set|bg-url)[^=]*)
88
+ That will catch the following known and more: data-cvpsrc|data-cvpset|data-thumb|data-bg-url|data-large_image|data-lazyload|data-source-url|data-srcsmall|data-srclarge|data-srcfull|data-slide-img|data-lazy-original
89
+
90
+
91
+ # in style
92
+
93
+ *pattern:*
94
+ ((?<=(?:((style\s*=)|(\<\s*style)).*background(-image)?\s*:\s*url\s*\([\"\']?)|(((style\s*=)|(\<\s*style)).*url.*,\s*url\([\"\']?))[^\"\']*\.(jpe?g|png))(?=[\"\'\s\>)])
95
+
96
+ *text:*
97
+ Notice: The pattern is meant for PHP and contains syntax which only works in some browsers. It works in Chrome. Not in Firefox.
98
+
99
+ The following should produce matches:
100
+
101
+ <style>#myphoto {background: url("http://example.com/image2.jpg")}</style>
102
+ <div style="background-image: url('http://example.com/image.png'), url("/image2.jpeg"), url(http://example.com/image3.jpg);"></div>
103
+ <div style="background: url ("http://example.com/image2.jpg")"></div>
104
+ <style>#myphoto {background: url("http://example.com/image2.jpg"), url("image2.jpeg"}</style>
105
+
106
+ Not these:
107
+ ----------
108
+
109
+ GIFs are disallowed:
110
+ <div style="background-image: url("http://example.com/image.gif"), url("http://example.com/image2.gif", url("image3.gif");"></div>
111
+
112
+ Querystrings are disallowed:
113
+ <div style="background-image: url('http://example.com/image.jpg?no-qs!')"></div>
114
+
115
+ HTML attributes disallowed:
116
+ <img src="header.jpg">
117
+
118
+ Go with style: background: url("http://example.com/image2.jpg")
119
+
120
+
121
+ And none of this either:
122
+
123
+ <div style="background-image: url('http://example.com/image.jpgelegi')"></div>
124
+ <img src="header.jpg">
125
+ <img src="/header.jpg">
126
+ <img src="http://example.com/header.jpeg" alt="">
127
+ <img src="http://example.com/header.jpg">
128
+ <img src="http://example.com/header.jpg"/>
129
+ <img src = "http://example.com/header.jpg">
130
+ <img src=http://example.com/header.jpg alt="">
131
+ <img src=http://example.com/header.jpg>
132
+ <img src=http://example.com/header.jpg alt="hello">
133
+ <img src=http://example.com/header.jpg />
134
+ <img src=http://example.com/header_.jpg/>
135
+ <picture><source src="http://example.com/header.jpg"><img src="http://example.com/header.jpg"></picture>
136
+ <input type="image" src="http://example.com/flamingo.jpg">
137
+ <iframe src="http://example.com/image.jpg"></iframe>
138
+
139
+ <img src="http://example.com/header.jpg" srcset="http://example.com/header.jpg 1000w">
140
+ <img src="http://example.com/header.jpg" srcset="http://example.com/header.jpg 1000w,http://example.com/header.jpg 1000w, http://example.com/header.jpg 2000w">
141
+ <img src="http://example.com/header.jpg" srcset="http://example.com/header-150x150.jpg 500w,http://example.com/header.jpg-300x300.jpg" sizes="(max-width: 480px) 100vw, (max-width: 900px) 33vw, 254px" alt="" width="100" height="100">
142
+
143
+ <img src="http://example.com/tegning.jpg.webp" alt="">
144
+ <img src="http://example.com/tegning.jpglidilo" alt="">
145
+ <img src="http://example.com/header.jpg/hi-res">
146
+ <img src=http://example.com/header.gif alt=nice-jpg>
147
+ <img src="http://example.com/tegning.webp" alt="">
148
+ src="http://example.com/header.jpeg"
149
+ <article data-src="http://example.com/header.jpg" />
150
+ <img><script src="http://example.com/script.js?preload=image.jpg">
151
+
152
+
153
+ I use another pattern for matching image urls in HTML attributes:
154
+ https://regexr.com/46jat
js/picturefill.min.js ADDED
@@ -0,0 +1,5 @@
1
+ /*! picturefill - v3.0.2 - 2016-02-12
2
+ * https://scottjehl.github.io/picturefill/
3
+ * Copyright (c) 2016 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT
4
+ */
5
+ !function(a){var b=navigator.userAgent;a.HTMLPictureElement&&/ecko/.test(b)&&b.match(/rv\:(\d+)/)&&RegExp.$1<45&&addEventListener("resize",function(){var b,c=document.createElement("source"),d=function(a){var b,d,e=a.parentNode;"PICTURE"===e.nodeName.toUpperCase()?(b=c.cloneNode(),e.insertBefore(b,e.firstElementChild),setTimeout(function(){e.removeChild(b)})):(!a._pfLastSize||a.offsetWidth>a._pfLastSize)&&(a._pfLastSize=a.offsetWidth,d=a.sizes,a.sizes+=",100vw",setTimeout(function(){a.sizes=d}))},e=function(){var a,b=document.querySelectorAll("picture > img, img[srcset][sizes]");for(a=0;a<b.length;a++)d(b[a])},f=function(){clearTimeout(b),b=setTimeout(e,99)},g=a.matchMedia&&matchMedia("(orientation: landscape)"),h=function(){f(),g&&g.addListener&&g.addListener(f)};return c.srcset="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",/^[c|i]|d#x2F;.test(document.readyState||"")?h():document.addEventListener("DOMContentLoaded",h),f}())}(window),function(a,b,c){"use strict";function d(a){return" "===a||" "===a||"\n"===a||"\f"===a||"\r"===a}function e(b,c){var d=new a.Image;return d.onerror=function(){A[b]=!1,ba()},d.onload=function(){A[b]=1===d.width,ba()},d.src=c,"pending"}function f(){M=!1,P=a.devicePixelRatio,N={},O={},s.DPR=P||1,Q.width=Math.max(a.innerWidth||0,z.clientWidth),Q.height=Math.max(a.innerHeight||0,z.clientHeight),Q.vw=Q.width/100,Q.vh=Q.height/100,r=[Q.height,Q.width,P].join("-"),Q.em=s.getEmValue(),Q.rem=Q.em}function g(a,b,c,d){var e,f,g,h;return"saveData"===B.algorithm?a>2.7?h=c+1:(f=b-c,e=Math.pow(a-.6,1.5),g=f*e,d&&(g+=.1*e),h=a+g):h=c>1?Math.sqrt(a*b):a,h>c}function h(a){var b,c=s.getSet(a),d=!1;"pending"!==c&&(d=r,c&&(b=s.setRes(c),s.applySetCandidate(b,a))),a[s.ns].evaled=d}function i(a,b){return a.res-b.res}function j(a,b,c){var d;return!c&&b&&(c=a[s.ns].sets,c=c&&c[c.length-1]),d=k(b,c),d&&(b=s.makeUrl(b),a[s.ns].curSrc=b,a[s.ns].curCan=d,d.res||aa(d,d.set.sizes)),d}function k(a,b){var c,d,e;if(a&&b)for(e=s.parseSet(b),a=s.makeUrl(a),c=0;c<e.length;c++)if(a===s.makeUrl(e[c].url)){d=e[c];break}return d}function l(a,b){var c,d,e,f,g=a.getElementsByTagName("source");for(c=0,d=g.length;d>c;c++)e=g[c],e[s.ns]=!0,f=e.getAttribute("srcset"),f&&b.push({srcset:f,media:e.getAttribute("media"),type:e.getAttribute("type"),sizes:e.getAttribute("sizes")})}function m(a,b){function c(b){var c,d=b.exec(a.substring(m));return d?(c=d[0],m+=c.length,c):void 0}function e(){var a,c,d,e,f,i,j,k,l,m=!1,o={};for(e=0;e<h.length;e++)f=h[e],i=f[f.length-1],j=f.substring(0,f.length-1),k=parseInt(j,10),l=parseFloat(j),X.test(j)&&"w"===i?((a||c)&&(m=!0),0===k?m=!0:a=k):Y.test(j)&&"x"===i?((a||c||d)&&(m=!0),0>l?m=!0:c=l):X.test(j)&&"h"===i?((d||c)&&(m=!0),0===k?m=!0:d=k):m=!0;m||(o.url=g,a&&(o.w=a),c&&(o.d=c),d&&(o.h=d),d||c||a||(o.d=1),1===o.d&&(b.has1x=!0),o.set=b,n.push(o))}function f(){for(c(T),i="",j="in descriptor";;){if(k=a.charAt(m),"in descriptor"===j)if(d(k))i&&(h.push(i),i="",j="after descriptor");else{if(","===k)return m+=1,i&&h.push(i),void e();if("("===k)i+=k,j="in parens";else{if(""===k)return i&&h.push(i),void e();i+=k}}else if("in parens"===j)if(")"===k)i+=k,j="in descriptor";else{if(""===k)return h.push(i),void e();i+=k}else if("after descriptor"===j)if(d(k));else{if(""===k)return void e();j="in descriptor",m-=1}m+=1}}for(var g,h,i,j,k,l=a.length,m=0,n=[];;){if(c(U),m>=l)return n;g=c(V),h=[],","===g.slice(-1)?(g=g.replace(W,""),e()):f()}}function n(a){function b(a){function b(){f&&(g.push(f),f="")}function c(){g[0]&&(h.push(g),g=[])}for(var e,f="",g=[],h=[],i=0,j=0,k=!1;;){if(e=a.charAt(j),""===e)return b(),c(),h;if(k){if("*"===e&&"/"===a[j+1]){k=!1,j+=2,b();continue}j+=1}else{if(d(e)){if(a.charAt(j-1)&&d(a.charAt(j-1))||!f){j+=1;continue}if(0===i){b(),j+=1;continue}e=" "}else if("("===e)i+=1;else if(")"===e)i-=1;else{if(","===e){b(),c(),j+=1;continue}if("/"===e&&"*"===a.charAt(j+1)){k=!0,j+=2;continue}}f+=e,j+=1}}}function c(a){return k.test(a)&&parseFloat(a)>=0?!0:l.test(a)?!0:"0"===a||"-0"===a||"+0"===a?!0:!1}var e,f,g,h,i,j,k=/^(?:[+-]?[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?(?:ch|cm|em|ex|in|mm|pc|pt|px|rem|vh|vmin|vmax|vw)#x2F;i,l=/^calc\((?:[0-9a-z \.\+\-\*\/\(\)]+)\)#x2F;i;for(f=b(a),g=f.length,e=0;g>e;e++)if(h=f[e],i=h[h.length-1],c(i)){if(j=i,h.pop(),0===h.length)return j;if(h=h.join(" "),s.matchesMedia(h))return j}return"100vw"}b.createElement("picture");var o,p,q,r,s={},t=!1,u=function(){},v=b.createElement("img"),w=v.getAttribute,x=v.setAttribute,y=v.removeAttribute,z=b.documentElement,A={},B={algorithm:""},C="data-pfsrc",D=C+"set",E=navigator.userAgent,F=/rident/.test(E)||/ecko/.test(E)&&E.match(/rv\:(\d+)/)&&RegExp.$1>35,G="currentSrc",H=/\s+\+?\d+(e\d+)?w/,I=/(\([^)]+\))?\s*(.+)/,J=a.picturefillCFG,K="position:absolute;left:0;visibility:hidden;display:block;padding:0;border:none;font-size:1em;width:1em;overflow:hidden;clip:rect(0px, 0px, 0px, 0px)",L="font-size:100%!important;",M=!0,N={},O={},P=a.devicePixelRatio,Q={px:1,"in":96},R=b.createElement("a"),S=!1,T=/^[ \t\n\r\u000c]+/,U=/^[, \t\n\r\u000c]+/,V=/^[^ \t\n\r\u000c]+/,W=/[,]+#x2F;,X=/^\d+#x2F;,Y=/^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?#x2F;,Z=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d||!1):a.attachEvent&&a.attachEvent("on"+b,c)},$=function(a){var b={};return function(c){return c in b||(b[c]=a(c)),b[c]}},_=function(){var a=/^([\d\.]+)(em|vw|px)#x2F;,b=function(){for(var a=arguments,b=0,c=a[0];++b in a;)c=c.replace(a[b],a[++b]);return c},c=$(function(a){return"return "+b((a||"").toLowerCase(),/\band\b/g,"&&",/,/g,"||",/min-([a-z-\s]+):/g,"e.$1>=",/max-([a-z-\s]+):/g,"e.$1<=",/calc([^)]+)/g,"($1)",/(\d+[\.]*[\d]*)([a-z]+)/g,"($1 * e.$2)",/^(?!(e.[a-z]|[0-9\.&=|><\+\-\*\(\)\/])).*/gi,"")+";"});return function(b,d){var e;if(!(b in N))if(N[b]=!1,d&&(e=b.match(a)))N[b]=e[1]*Q[e[2]];else try{N[b]=new Function("e",c(b))(Q)}catch(f){}return N[b]}}(),aa=function(a,b){return a.w?(a.cWidth=s.calcListLength(b||"100vw"),a.res=a.w/a.cWidth):a.res=a.d,a},ba=function(a){if(t){var c,d,e,f=a||{};if(f.elements&&1===f.elements.nodeType&&("IMG"===f.elements.nodeName.toUpperCase()?f.elements=[f.elements]:(f.context=f.elements,f.elements=null)),c=f.elements||s.qsa(f.context||b,f.reevaluate||f.reselect?s.sel:s.selShort),e=c.length){for(s.setupRun(f),S=!0,d=0;e>d;d++)s.fillImg(c[d],f);s.teardownRun(f)}}};o=a.console&&console.warn?function(a){console.warn(a)}:u,G in v||(G="src"),A["image/jpeg"]=!0,A["image/gif"]=!0,A["image/png"]=!0,A["image/svg+xml"]=b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1"),s.ns=("pf"+(new Date).getTime()).substr(0,9),s.supSrcset="srcset"in v,s.supSizes="sizes"in v,s.supPicture=!!a.HTMLPictureElement,s.supSrcset&&s.supPicture&&!s.supSizes&&!function(a){v.srcset="data:,a",a.src="data:,a",s.supSrcset=v.complete===a.complete,s.supPicture=s.supSrcset&&s.supPicture}(b.createElement("img")),s.supSrcset&&!s.supSizes?!function(){var a="data:image/gif;base64,R0lGODlhAgABAPAAAP///wAAACH5BAAAAAAALAAAAAACAAEAAAICBAoAOw==",c="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",d=b.createElement("img"),e=function(){var a=d.width;2===a&&(s.supSizes=!0),q=s.supSrcset&&!s.supSizes,t=!0,setTimeout(ba)};d.onload=e,d.onerror=e,d.setAttribute("sizes","9px"),d.srcset=c+" 1w,"+a+" 9w",d.src=c}():t=!0,s.selShort="picture>img,img[srcset]",s.sel=s.selShort,s.cfg=B,s.DPR=P||1,s.u=Q,s.types=A,s.setSize=u,s.makeUrl=$(function(a){return R.href=a,R.href}),s.qsa=function(a,b){return"querySelector"in a?a.querySelectorAll(b):[]},s.matchesMedia=function(){return a.matchMedia&&(matchMedia("(min-width: 0.1em)")||{}).matches?s.matchesMedia=function(a){return!a||matchMedia(a).matches}:s.matchesMedia=s.mMQ,s.matchesMedia.apply(this,arguments)},s.mMQ=function(a){return a?_(a):!0},s.calcLength=function(a){var b=_(a,!0)||!1;return 0>b&&(b=!1),b},s.supportsType=function(a){return a?A[a]:!0},s.parseSize=$(function(a){var b=(a||"").match(I);return{media:b&&b[1],length:b&&b[2]}}),s.parseSet=function(a){return a.cands||(a.cands=m(a.srcset,a)),a.cands},s.getEmValue=function(){var a;if(!p&&(a=b.body)){var c=b.createElement("div"),d=z.style.cssText,e=a.style.cssText;c.style.cssText=K,z.style.cssText=L,a.style.cssText=L,a.appendChild(c),p=c.offsetWidth,a.removeChild(c),p=parseFloat(p,10),z.style.cssText=d,a.style.cssText=e}return p||16},s.calcListLength=function(a){if(!(a in O)||B.uT){var b=s.calcLength(n(a));O[a]=b?b:Q.width}return O[a]},s.setRes=function(a){var b;if(a){b=s.parseSet(a);for(var c=0,d=b.length;d>c;c++)aa(b[c],a.sizes)}return b},s.setRes.res=aa,s.applySetCandidate=function(a,b){if(a.length){var c,d,e,f,h,k,l,m,n,o=b[s.ns],p=s.DPR;if(k=o.curSrc||b[G],l=o.curCan||j(b,k,a[0].set),l&&l.set===a[0].set&&(n=F&&!b.complete&&l.res-.1>p,n||(l.cached=!0,l.res>=p&&(h=l))),!h)for(a.sort(i),f=a.length,h=a[f-1],d=0;f>d;d++)if(c=a[d],c.res>=p){e=d-1,h=a[e]&&(n||k!==s.makeUrl(c.url))&&g(a[e].res,c.res,p,a[e].cached)?a[e]:c;break}h&&(m=s.makeUrl(h.url),o.curSrc=m,o.curCan=h,m!==k&&s.setSrc(b,h),s.setSize(b))}},s.setSrc=function(a,b){var c;a.src=b.url,"image/svg+xml"===b.set.type&&(c=a.style.width,a.style.width=a.offsetWidth+1+"px",a.offsetWidth+1&&(a.style.width=c))},s.getSet=function(a){var b,c,d,e=!1,f=a[s.ns].sets;for(b=0;b<f.length&&!e;b++)if(c=f[b],c.srcset&&s.matchesMedia(c.media)&&(d=s.supportsType(c.type))){"pending"===d&&(c=d),e=c;break}return e},s.parseSets=function(a,b,d){var e,f,g,h,i=b&&"PICTURE"===b.nodeName.toUpperCase(),j=a[s.ns];(j.src===c||d.src)&&(j.src=w.call(a,"src"),j.src?x.call(a,C,j.src):y.call(a,C)),(j.srcset===c||d.srcset||!s.supSrcset||a.srcset)&&(e=w.call(a,"srcset"),j.srcset=e,h=!0),j.sets=[],i&&(j.pic=!0,l(b,j.sets)),j.srcset?(f={srcset:j.srcset,sizes:w.call(a,"sizes")},j.sets.push(f),g=(q||j.src)&&H.test(j.srcset||""),g||!j.src||k(j.src,f)||f.has1x||(f.srcset+=", "+j.src,f.cands.push({url:j.src,d:1,set:f}))):j.src&&j.sets.push({srcset:j.src,sizes:null}),j.curCan=null,j.curSrc=c,j.supported=!(i||f&&!s.supSrcset||g&&!s.supSizes),h&&s.supSrcset&&!j.supported&&(e?(x.call(a,D,e),a.srcset=""):y.call(a,D)),j.supported&&!j.srcset&&(!j.src&&a.src||a.src!==s.makeUrl(j.src))&&(null===j.src?a.removeAttribute("src"):a.src=j.src),j.parsed=!0},s.fillImg=function(a,b){var c,d=b.reselect||b.reevaluate;a[s.ns]||(a[s.ns]={}),c=a[s.ns],(d||c.evaled!==r)&&((!c.parsed||b.reevaluate)&&s.parseSets(a,a.parentNode,b),c.supported?c.evaled=r:h(a))},s.setupRun=function(){(!S||M||P!==a.devicePixelRatio)&&f()},s.supPicture?(ba=u,s.fillImg=u):!function(){var c,d=a.attachEvent?/d$|^c/:/d$|^c|^i/,e=function(){var a=b.readyState||"";f=setTimeout(e,"loading"===a?200:999),b.body&&(s.fillImgs(),c=c||d.test(a),c&&clearTimeout(f))},f=setTimeout(e,b.body?9:99),g=function(a,b){var c,d,e=function(){var f=new Date-d;b>f?c=setTimeout(e,b-f):(c=null,a())};return function(){d=new Date,c||(c=setTimeout(e,b))}},h=z.clientHeight,i=function(){M=Math.max(a.innerWidth||0,z.clientWidth)!==Q.width||z.clientHeight!==h,h=z.clientHeight,M&&s.fillImgs()};Z(a,"resize",g(i,99)),Z(b,"readystatechange",e)}(),s.picturefill=ba,s.fillImgs=ba,s.teardownRun=u,ba._=s,a.picturefillCFG={pf:s,push:function(a){var b=a.shift();"function"==typeof s[b]?s[b].apply(s,a):(B[b]=a[0],S&&s.fillImgs({reselect:!0}))}};for(;J&&J.length;)a.picturefillCFG.push(J.shift());a.picturefill=ba,"object"==typeof module&&"object"==typeof module.exports?module.exports=ba:"function"==typeof define&&define.amd&&define("picturefill",function(){return ba}),s.supPicture||(A["image/webp"]=e("image/webp","data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA=="))}(window,document);
lib/admin.php CHANGED
@@ -2,7 +2,9 @@
2
use \WebPExpress\State;
3
4
// When an update requires a migration, the number should be increased
5
- define('WEBPEXPRESS_MIGRATION_VERSION', '4');
6
7
if (WEBPEXPRESS_MIGRATION_VERSION != get_option('webp-express-migration-version', 0)) {
8
// run migration logic
2
use \WebPExpress\State;
3
4
// When an update requires a migration, the number should be increased
5
+ define('WEBPEXPRESS_MIGRATION_VERSION', '5');
6
+
7
+ //update_option('webp-express-migration-version', '4');
8
9
if (WEBPEXPRESS_MIGRATION_VERSION != get_option('webp-express-migration-version', 0)) {
10
// run migration logic
lib/alter-html.php ADDED
@@ -0,0 +1,75 @@
1
+ <?php
2
+
3
+ function webPExpressAlterHtml($content) {
4
+ // Don't do anything with the RSS feed.
5
+ if (is_feed()) {
6
+ return $content;
7
+ }
8
+
9
+ if (get_option('webp-express-alter-html-replacement') == 'picture') {
10
+ if(function_exists('is_amp_endpoint') && is_amp_endpoint()) {
11
+ //for AMP pages the <picture> tag is not allowed
12
+ return $content;
13
+ }
14
+ /*if (is_admin() ) {
15
+ return $content;
16
+ }*/
17
+ require_once __DIR__ . '/classes/AlterHtmlPicture.php';
18
+ return \WebPExpress\AlterHtmlPicture::alter($content);
19
+ } else {
20
+ require_once __DIR__ . '/classes/AlterHtmlImageUrls.php';
21
+ return \WebPExpress\AlterHtmlImageUrls::alter($content);
22
+
23
+ }
24
+ }
25
+
26
+ function webpExpressOutputBuffer() {
27
+ if (!is_admin() || (function_exists("wp_doing_ajax") && wp_doing_ajax()) || (defined( 'DOING_AJAX' ) && DOING_AJAX)) {
28
+ ob_start('webPExpressAlterHtml');
29
+ }
30
+ }
31
+
32
+ function webpExpressAddPictureJs() {
33
+ // Don't do anything with the RSS feed.
34
+ // - and no need for PictureJs in the admin
35
+ if ( is_feed() || is_admin() ) { return; }
36
+
37
+ echo '<script>'
38
+ . 'document.createElement( "picture" );'
39
+ . 'if(!window.HTMLPictureElement && document.addEventListener) {'
40
+ . 'window.addEventListener("DOMContentLoaded", function() {'
41
+ . 'var s = document.createElement("script");'
42
+ . 's.src = "' . plugins_url('/js/picturefill.min.js', __FILE__) . '";'
43
+ . 'document.body.appendChild(s);'
44
+ . '});'
45
+ . '}'
46
+ . '</script>';
47
+ }
48
+
49
+
50
+ if (get_option('webp-express-alter-html-replacement') == 'picture') {
51
+ add_action( 'wp_head', 'webpExpressAddPictureJs');
52
+ }
53
+
54
+ if (get_option('webp-express-alter-html-hooks', 'ob') == 'ob') {
55
+ /* TODO:
56
+ Which hook should we use, and should we make it optional?
57
+ - Cache enabler uses 'template_redirect'
58
+ - ShortPixes uses 'init'
59
+
60
+ We go with template_redirect now, because it is the "innermost".
61
+ This lowers the risk of problems with plugins used rewriting URLs to point to CDN.
62
+ (We need to process the output *before* the other plugin has rewritten the URLs,
63
+ if the "Only for webps that exists" feature is enabled)
64
+ */
65
+ add_action( 'init', 'webpExpressOutputBuffer', 1 );
66
+ //add_action( 'template_redirect', 'webpExpressOutputBuffer', 1 );
67
+
68
+ } else {
69
+ add_filter( 'the_content', 'webPExpressAlterHtml', 10000 ); // priority big, so it will be executed last
70
+ add_filter( 'the_excerpt', 'webPExpressAlterHtml', 10000 );
71
+ add_filter( 'post_thumbnail_html', 'webPExpressAlterHtml');
72
+ }
73
+
74
+ //echo wp_doing_ajax() ? 'ajax' : 'no ajax'; exit;
75
+ //echo is_feed() ? 'feed' : 'no feed'; exit;
lib/classes/AlterHtmlHelper.php ADDED
@@ -0,0 +1,257 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ //use AlterHtmlInit;
6
+
7
+ include_once "Paths.php";
8
+ use \WebPExpress\Paths;
9
+
10
+ include_once "PathHelper.php";
11
+ use \WebPExpress\PathHelper;
12
+
13
+ class AlterHtmlHelper
14
+ {
15
+
16
+ public static $options;
17
+ /*
18
+ public static function hasWebP($src)
19
+ {
20
+ return true;
21
+ }
22
+
23
+ public static function inUploadDir($src)
24
+ {
25
+ $upload_dir = wp_upload_dir();
26
+ $src_url = parse_url($upload_dir['baseurl']);
27
+ $upload_path = $src_url['path'];
28
+
29
+ return (strpos($src, $upload_path) !== false );
30
+
31
+ }
32
+
33
+ public static function checkSrc($src)
34
+ {
35
+ self::$options = \WebPExpress\AlterHtmlInit::self::$options();
36
+
37
+
38
+ if (self::$options['destination-folder'] == 'mingled') {
39
+
40
+ }
41
+ }
42
+ */
43
+ /**
44
+ * Gets relative path between a base url and another.
45
+ * Returns false if the url isn't a subpath
46
+ *
47
+ * @param $imageUrl (ie "http://example.com/wp-content/image.jpg")
48
+ * @param $baseUrl (ie "http://example.com/wp-content")
49
+ * @return path or false (ie "/image.jpg")
50
+ */
51
+ public static function getRelUrlPath($imageUrl, $baseUrl)
52
+ {
53
+
54
+ $baseUrlComponents = parse_url($baseUrl);
55
+ /* ie:
56
+ (
57
+ [scheme] => http
58
+ [host] => we0
59
+ [path] => /wordpress/uploads-moved
60
+ )*/
61
+
62
+ $imageUrlComponents = parse_url($imageUrl);
63
+ /* ie:
64
+ (
65
+ [scheme] => http
66
+ [host] => we0
67
+ [path] => /wordpress/uploads-moved/logo.jpg
68
+ )*/
69
+ if ($baseUrlComponents['host'] != $imageUrlComponents['host']) {
70
+ return false;
71
+ }
72
+
73
+ // Check if path begins with base path
74
+ if (strpos($imageUrlComponents['path'], $baseUrlComponents['path']) !== 0) {
75
+ return false;
76
+ }
77
+
78
+ // Remove base path from path (we know it begins with basepath, from previous check)
79
+ return substr($imageUrlComponents['path'], strlen($baseUrlComponents['path']));
80
+
81
+ }
82
+
83
+ /**
84
+ * Looks if $imageUrl is rooted in $baseUrl and if the file is there
85
+ *
86
+ *
87
+ * @param $imageUrl (ie http://example.com/wp-content/image.jpg)
88
+ * @param $baseUrl (ie http://example.com/wp-content)
89
+ * @param $baseDir (ie /var/www/example.com/wp-content)
90
+ */
91
+ public static function isImageUrlHere($imageUrl, $baseUrl, $baseDir)
92
+ {
93
+
94
+ $srcPathRel = self::getRelUrlPath($imageUrl, $baseUrl);
95
+
96
+ if ($srcPathRel === false) {
97
+ return false;
98
+ }
99
+
100
+ // Calculate file path to src
101
+ $srcPathAbs = $baseDir . $srcPathRel;
102
+ //return 'dyt:' . $srcPathAbs;
103
+
104
+ // Check that src file exists
105
+ if (!@file_exists($srcPathAbs)) {
106
+ return false;
107
+ }
108
+
109
+ return true;
110
+
111
+ }
112
+
113
+
114
+ public static function isSourceInUpload($src)
115
+ {
116
+ /* $src is ie http://we0/wp-content-moved/themes/twentyseventeen/assets/images/header.jpg */
117
+
118
+ $uploadDir = wp_upload_dir();
119
+ /* ie:
120
+
121
+ [path] => /var/www/webp-express-tests/we0/wordpress/uploads-moved
122
+ [url] => http://we0/wordpress/uploads-moved
123
+ [subdir] =>
124
+ [basedir] => /var/www/webp-express-tests/we0/wordpress/uploads-moved
125
+ [baseurl] => http://we0/wordpress/uploads-moved
126
+ [error] =>
127
+ */
128
+
129
+ return self::isImageUrlHere($src, $uploadDir['baseurl'], $uploadDir['basedir']);
130
+ }
131
+
132
+
133
+ /**
134
+ * Get url for webp, given a certain baseUrl / baseDir.
135
+ * Base can for example be uploads or wp-content.
136
+ *
137
+ * returns false
138
+ * - if no source file found in that base
139
+ * - or webp file isn't there and the `only-for-webps-that-exists` option is set
140
+ *
141
+ * @param $imageUrl (ie http://example.com/wp-content/image.jpg)
142
+ * @param $baseUrl (ie http://example.com/wp-content)
143
+ * @param $baseDir (ie /var/www/example.com/wp-content)
144
+ */
145
+ private static function getWebPUrlInBase($sourceUrl, $baseUrl, $baseDir)
146
+ {
147
+
148
+ $srcPathRel = self::getRelUrlPath($sourceUrl, $baseUrl);
149
+
150
+ if ($srcPathRel === false) {
151
+ return false;
152
+ }
153
+
154
+ // Calculate file path to src
155
+ $srcPathAbs = $baseDir . $srcPathRel;
156
+
157
+ // Check that src file exists
158
+ if (!@file_exists($srcPathAbs)) {
159
+ return false;
160
+ }
161
+
162
+
163
+ // Calculate $destPathAbs and $destUrl
164
+ // -------------------------------------
165
+ $inUpload = self::isSourceInUpload($sourceUrl);
166
+
167
+ if ((self::$options['destination-folder'] == 'mingled') && $inUpload) {
168
+ // mingled
169
+ if (self::$options['destination-extension'] == 'append') {
170
+ $destPathAbs = $srcPathAbs . '.webp';
171
+ $destUrl = $sourceUrl . '.webp';
172
+ } else {
173
+ $destPathAbs = preg_replace('/\\.(png|jpe?g)#x2F;', '', $srcPathAbs) . '.webp';
174
+ $destUrl = preg_replace('/\\.(png|jpe?g)#x2F;', '', $sourceUrl) . '.webp';
175
+ }
176
+ } else {
177
+ // separate (images that are not in upload are always put in separate)
178
+
179
+ $relPathFromDocRoot = '/webp-express/webp-images/doc-root/';
180
+ $relPathFromDocRoot .= PathHelper::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $baseDir) . $srcPathRel;
181
+
182
+ list ($contentDirAbs, $contentUrl) = self::$options['bases']['content'];
183
+
184
+ $destPathAbs = $contentDirAbs . $relPathFromDocRoot . '.webp';
185
+ $destUrl = $contentUrl . $relPathFromDocRoot . '.webp';
186
+ }
187
+
188
+ $webpMustExist = self::$options['only-for-webps-that-exists'];
189
+ if ($webpMustExist && (!@file_exists($destPathAbs))) {
190
+ return false;
191
+ }
192
+ return $destUrl;
193
+
194
+ }
195
+
196
+
197
+ /**
198
+ * Get url for webp
199
+ * returns second argument if no webp
200
+ *
201
+ * @param $imageUrl (ie http://example.com/wp-content/image.jpg)
202
+ * @param $baseUrl (ie http://example.com/wp-content)
203
+ * @param $baseDir (ie /var/www/example.com/wp-content)
204
+ */
205
+ public static function getWebPUrl($sourceUrl, $returnValueOnFail)
206
+ {
207
+ if (!isset(self::$options)) {
208
+ self::$options = json_decode(get_option('webp-express-alter-html-options', null), true);
209
+ }
210
+
211
+
212
+ // Currently we do not handle relative urls - so we skip
213
+ if (!preg_match('#^https?://#', $sourceUrl)) {
214
+ return $returnValueOnFail;
215
+ }
216
+
217
+ switch (self::$options['image-types']) {
218
+ case 0:
219
+ return $returnValueOnFail;
220
+ case 1:
221
+ if (!preg_match('#(jpe?g)$#', $sourceUrl)) {
222
+ return $returnValueOnFail;
223
+ }
224
+ break;
225
+ case 2:
226
+ if (!preg_match('#(png)$#', $sourceUrl)) {
227
+ return $returnValueOnFail;
228
+ }
229
+ break;
230
+ case 3:
231
+ if (!preg_match('#(jpe?g|png)$#', $sourceUrl)) {
232
+ return $returnValueOnFail;
233
+ }
234
+ break;
235
+ }
236
+
237
+ if ((self::$options['only-for-webp-enabled-browsers']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') === false)) {
238
+ return $returnValueOnFail;
239
+ }
240
+
241
+ foreach (self::$options['bases'] as $id => list($baseDir, $baseUrl)) {
242
+
243
+ $result = self::getWebPUrlInBase($sourceUrl, $baseUrl, $baseDir);
244
+ if ($result !== false) {
245
+ return $result;
246
+ }
247
+ }
248
+ return $returnValueOnFail;
249
+ }
250
+
251
+ /*
252
+ public static function getWebPUrlOrSame($sourceUrl, $returnValueOnFail)
253
+ {
254
+ return self::getWebPUrl($sourceUrl, $sourceUrl);
255
+ }*/
256
+
257
+ }
lib/classes/AlterHtmlImageUrls.php ADDED
@@ -0,0 +1,83 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "Paths.php";
6
+ use \WebPExpress\Paths;
7
+
8
+ use \WebPExpress\AlterHtmlInit;
9
+
10
+ /**
11
+ * Class AlterHtmlImageUrls - convert image urls to webp
12
+ * Based this code on code from the Cache Enabler plugin
13
+ */
14
+
15
+ use \WebPExpress\AlterHtmlHelper;
16
+ //use \WebPExpress\ImageUrlsReplacer;
17
+ use DOMUtilForWebP\ImageUrlReplacer;
18
+
19
+ class AlterHtmlImageUrls extends ImageUrlReplacer
20
+ {
21
+ public function replaceUrl($url) {
22
+ return AlterHtmlHelper::getWebPUrl($url, null);
23
+ }
24
+
25
+ public function attributeFilter($attrName) {
26
+ // Allow "src", "srcset" and data-attributes that smells like they are used for images
27
+ // The following rule matches all attributes used for lazy loading images that we know of
28
+ return preg_match('#^(src|srcset|(data-[^=]*(lazy|small|slide|img|large|src|thumb|source|set|bg-url)[^=]*))$#i', $attrName);
29
+
30
+ // If you want to limit it further, only allowing attributes known to be used for lazy load,
31
+ // use the following regex instead:
32
+ //return preg_match('#^(src|srcset|data-(src|srcset|cvpsrc|cvpset|thumb|bg-url|large_image|lazyload|source-url|srcsmall|srclarge|srcfull|slide-img|lazy-original))$#i', $attrName);
33
+ }
34
+
35
+ /*
36
+ public static function alter($content) {
37
+ require_once "AlterHtmlHelper.php";
38
+
39
+
40
+ //Find image urls in HTML attributes.
41
+ //- Ignore URLs with query string
42
+ //- Matches src, src-set and data-attributes in a limited set of tags
43
+ //- Only jpeg, jpg or png (NO GIFs)
44
+ //- Both relative and absolute URLs are matched
45
+ //- tag name is returned in $1, url in amp;
46
+
47
+ //Pattern for attributes can be tested here: https://regexr.com/46jat
48
+ //PS: Based on regex found in Cache Enabler 1.3.2: https://regexr.com/46isf
49
+
50
+ //TODO: Dont match PNG, unless choosen
51
+ $regex_rule = '#(?<=(?:<(img|source|input|iframe)[^>]*\s+(src|srcset|data-[^=]*)\s*=\s*[\"\']?))(?:[^\"\'>]+)(\.png|\.jp[e]?g)(\s\d+w)?(?=\/?[\"\'\s\>])#';
52
+ return preg_replace_callback($regex_rule, 'self::replaceCallback', $content);
53
+
54
+ // TODO: css too. I already got the regex ready: https://regexr.com/46jcg
55
+
56
+
57
+ }
58
+
59
+ /*
60
+ private static function replaceCallback($match) {
61
+ list($attrValue, $attrName) = $match;
62
+
63
+ // A data attribute contain a srcset or single url.
64
+ // We should probably examine the attrValue further. For now, we however just use the attrName as a clue.
65
+ // If it contains "set", we handle it as a srcset. This takes care of both "data-cvpset" and "srcset"
66
+ if (strpos($attrName, 'set') > 0) {
67
+ $srcsetArr = explode(',', $attrValue);
68
+ foreach ($srcsetArr as $i => $srcSetEntry) {
69
+ // $srcSetEntry is ie "http://example.com/image.jpg 520w"
70
+ list($src, $width) = preg_split('/\s+/', trim($srcSetEntry)); // $width might not be set, but thats ok.
71
+
72
+ $webpUrl = \WebPExpress\AlterHtmlHelper::getWebPUrl($src, false);
73
+ if ($webpUrl !== false) {
74
+ $srcsetArr[$i] = $webpUrl . (isset($width) ? ' ' . $width : '');
75
+ }
76
+ }
77
+ return implode(', ', $srcsetArr);
78
+ } else {
79
+ return \WebPExpress\AlterHtmlHelper::getWebPUrl($attrValue, $attrValue);
80
+ }
81
+ }*/
82
+
83
+ }
lib/classes/AlterHtmlInit.php ADDED
@@ -0,0 +1,126 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "AlterHtmlHelper.php";
6
+ use AlterHtmlHelper;
7
+
8
+ class AlterHtmlInit
9
+ {
10
+ public static $options = null;
11
+
12
+ public static function startOutputBuffer()
13
+ {
14
+ if (!is_admin() || (function_exists("wp_doing_ajax") && wp_doing_ajax()) || (defined( 'DOING_AJAX' ) && DOING_AJAX)) {
15
+ ob_start('self::alterHtml');
16
+ }
17
+ }
18
+
19
+ public static function alterHtml($content)
20
+ {
21
+ // Don't do anything with the RSS feed.
22
+ if (is_feed()) {
23
+ return $content;
24
+ }
25
+
26
+ if (get_option('webp-express-alter-html-replacement') == 'picture') {
27
+ if(function_exists('is_amp_endpoint') && is_amp_endpoint()) {
28
+ //for AMP pages the <picture> tag is not allowed
29
+ return $content;
30
+ }
31
+ /*if (is_admin() ) {
32
+ return $content;
33
+ }*/
34
+ }
35
+
36
+ if (!isset(self::$options)) {
37
+ self::$options = json_decode(get_option('webp-express-alter-html-options', null), true);
38
+ //AlterHtmlHelper::$options = self::$options;
39
+ }
40
+
41
+ if (self::$options == null) {
42
+ return $content;
43
+ }
44
+
45
+ if (get_option('webp-express-alter-html-replacement') == 'picture') {
46
+ require_once __DIR__ . "../../../vendor/autoload.php";
47
+ require_once __DIR__ . '/AlterHtmlHelper.php';
48
+ require_once __DIR__ . '/AlterHtmlPicture.php';
49
+ return \WebPExpress\AlterHtmlPicture::replace($content);
50
+ } else {
51
+ require_once __DIR__ . "../../../vendor/autoload.php";
52
+ require_once __DIR__ . '/AlterHtmlHelper.php';
53
+ require_once __DIR__ . '/AlterHtmlImageUrls.php';
54
+ return \WebPExpress\AlterHtmlImageUrls::replace($content);
55
+ }
56
+ }
57
+
58
+ public static function addPictureJs()
59
+ {
60
+ // Don't do anything with the RSS feed.
61
+ // - and no need for PictureJs in the admin
62
+ if ( is_feed() || is_admin() ) { return; }
63
+
64
+ echo '<script>'
65
+ . 'document.createElement( "picture" );'
66
+ . 'if(!window.HTMLPictureElement && document.addEventListener) {'
67
+ . 'window.addEventListener("DOMContentLoaded", function() {'
68
+ . 'var s = document.createElement("script");'
69
+ . 's.src = "' . plugins_url('/js/picturefill.min.js', __FILE__) . '";'
70
+ . 'document.body.appendChild(s);'
71
+ . '});'
72
+ . '}'
73
+ . '</script>';
74
+ }
75
+
76
+ public static function setHooks() {
77
+
78
+ if (get_option('webp-express-alter-html-replacement') == 'picture') {
79
+ // add_action( 'wp_head', '\\WebPExpress\\AlterHtmlInit::addPictureJs');
80
+ }
81
+
82
+ if (get_option('webp-express-alter-html-hooks', 'ob') == 'ob') {
83
+ /* TODO:
84
+ Which hook should we use, and should we make it optional?
85
+ - Cache enabler uses 'template_redirect'
86
+ - ShortPixes uses 'init'
87
+
88
+ We go with template_redirect now, because it is the "innermost".
89
+ This lowers the risk of problems with plugins used rewriting URLs to point to CDN.
90
+ (We need to process the output *before* the other plugin has rewritten the URLs,
91
+ if the "Only for webps that exists" feature is enabled)
92
+ */
93
+ add_action( 'init', '\\WebPExpress\\AlterHtmlInit::startOutputBuffer', 1 );
94
+ add_action( 'template_redirect', '\\WebPExpress\\AlterHtmlInit::startOutputBuffer', 10000 );
95
+
96
+ } else {
97
+ add_filter( 'the_content', '\\WebPExpress\\AlterHtmlInit::alterHtml', 99999 ); // priority big, so it will be executed last
98
+ add_filter( 'the_excerpt', '\\WebPExpress\\AlterHtmlInit::alterHtml', 99999 );
99
+ add_filter( 'post_thumbnail_html', '\\WebPExpress\\AlterHtmlInit::alterHtml', 99999);
100
+
101
+
102
+ /*
103
+ TODO:
104
+ check out these hooks (used by Jecpack, in class.photon.php)
105
+
106
+ // Images in post content and galleries
107
+ add_filter( 'the_content', array( __CLASS__, 'filter_the_content' ), 999999 );
108
+ add_filter( 'get_post_galleries', array( __CLASS__, 'filter_the_galleries' ), 999999 );
109
+ add_filter( 'widget_media_image_instance', array( __CLASS__, 'filter_the_image_widget' ), 999999 );
110
+
111
+ // Core image retrieval
112
+ add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
113
+ add_filter( 'rest_request_before_callbacks', array( $this, 'should_rest_photon_image_downsize' ), 10, 3 );
114
+ add_filter( 'rest_request_after_callbacks', array( $this, 'cleanup_rest_photon_image_downsize' ) );
115
+
116
+ // Responsive image srcset substitution
117
+ add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 10, 5 );
118
+ add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still easily filter.
119
+
120
+ // Helpers for maniuplated images
121
+ add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
122
+ */
123
+ }
124
+ }
125
+
126
+ }
lib/classes/AlterHtmlPicture.php ADDED
@@ -0,0 +1,18 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ /**
6
+ * Class AlterHtmlPicture - convert an <img> tag to a <picture> tag and add the webp versions of the images
7
+ * Based this code on code from the ShortPixel plugin, which used code from Responsify WP plugin
8
+ */
9
+
10
+ use \WebPExpress\AlterHtmlHelper;
11
+ use DOMUtilForWebP\PictureTags;
12
+
13
+ class AlterHtmlPicture extends PictureTags
14
+ {
15
+ public function replaceUrl($url) {
16
+ return AlterHtmlHelper::getWebPUrl($url, null);
17
+ }
18
+ }
lib/classes/CacheMover.php CHANGED
@@ -8,7 +8,6 @@ use \WebPExpress\FileHelper;
8
include_once "Paths.php";
9
use \WebPExpress\Paths;
10
11
-
12
class CacheMover
13
{
14
@@ -22,6 +21,33 @@ class CacheMover
22
}
23
}
24
25
/**
26
* Move cache because of change in options.
27
* Only move the upload folder
@@ -52,7 +78,11 @@ class CacheMover
52
echo 'ext:' . $fromExt . ' => ' . $toExt . '<br>';
53
echo '</pre>';*/
54
55
- return self::moveRecursively($fromDir, $toDir, $srcDir, $fromExt, $toExt);
56
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
57
}
58
@@ -81,10 +111,10 @@ class CacheMover
81
$filename = $fileIterator->getFilename();
82
83
if (($filename != ".") && ($filename != "..")) {
84
- //$filePerm = FileHelper::filePermWithFallback($filePerm, 0777);
85
86
if (@is_dir($fromDir . "/" . $filename)) {
87
- list($r1, $r2) = self::moveRecursively($fromDir . "/" . $filename, $toDir . "/" . $filename, $srcDir . "/" . $filename, $fromExt, $toExt, 0);
88
$numFilesMoved += $r1;
89
$numFilesFailedMoving += $r2;
90
@@ -136,7 +166,8 @@ class CacheMover
136
137
if ($newFilename !== null) {
138
//echo 'moving to: ' . $toDir . '/' .$newFilename . "<br>";
139
- if (@rename($fromDir . "/" . $filename, $toDir . "/" . $newFilename)) {
140
$numFilesMoved++;
141
} else {
142
$numFilesFailedMoving++;
8
include_once "Paths.php";
9
use \WebPExpress\Paths;
10
11
class CacheMover
12
{
13
21
}
22
}
23
24
+ /**
25
+ * Sets permission, uid and gid of all subfolders/files of a dir to same as the dir
26
+ * (but for files, do not set executable flag)
27
+ */
28
+ public static function chmodFixSubDirs($dir, $alsoSetOnDirs)
29
+ {
30
+ $dirPerm = FileHelper::filePermWithFallback($dir, 0775);
31
+ $filePerm = $dirPerm & 0666; // set executable flags to 0
32
+ /*echo 'dir:' . $dir . "\n";
33
+ echo 'Dir perm:' . FileHelper::humanReadableFilePerm($dirPerm) . "\n";
34
+ echo 'File perm:' . FileHelper::humanReadableFilePerm($filePerm) . "\n";*/
35
+ //return;
36
+
37
+ $stat = @stat($dir);
38
+ $uid = null;
39
+ $gid = null;
40
+ if ($stat !== false) {
41
+ if (isset($stat['uid'])) {
42
+ $uid = $stat['uid'];
43
+ }
44
+ if (isset($stat['gid'])) {
45
+ $uid = $stat['gid'];
46
+ }
47
+ }
48
+ FileHelper::chmod_r($dir, $dirPerm, $filePerm, $uid, $gid, '#\.webp$#', ($alsoSetOnDirs ? null : '#^$#'));
49
+ }
50
+
51
/**
52
* Move cache because of change in options.
53
* Only move the upload folder
78
echo 'ext:' . $fromExt . ' => ' . $toExt . '<br>';
79
echo '</pre>';*/
80
81
+
82
+ $result = self::moveRecursively($fromDir, $toDir, $srcDir, $fromExt, $toExt);
83
+ self::chmodFixSubDirs($toDir, ($newConfig['destination-folder'] == 'separate'));
84
+
85
+ return $result;
86
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
87
}
88
111
$filename = $fileIterator->getFilename();
112
113
if (($filename != ".") && ($filename != "..")) {
114
+ //$filePerm = FileHelper::filePermWithFallback($filename, 0777);
115
116
if (@is_dir($fromDir . "/" . $filename)) {
117
+ list($r1, $r2) = self::moveRecursively($fromDir . "/" . $filename, $toDir . "/" . $filename, $srcDir . "/" . $filename, $fromExt, $toExt);
118
$numFilesMoved += $r1;
119
$numFilesFailedMoving += $r2;
120
166
167
if ($newFilename !== null) {
168
//echo 'moving to: ' . $toDir . '/' .$newFilename . "<br>";
169
+ $toFilename = $toDir . "/" . $newFilename;
170
+ if (@rename($fromDir . "/" . $filename, $toFilename)) {
171
$numFilesMoved++;
172
} else {
173
$numFilesFailedMoving++;
lib/classes/Config.php CHANGED
@@ -45,7 +45,7 @@ class Config
45
46
public static function saveJSONOptions($filename, $obj)
47
{
48
- $result = @file_put_contents($filename, json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK));
49
/*if ($result === false) {
50
echo 'COULD NOT' . $filename;
51
}*/
@@ -67,7 +67,7 @@ class Config
67
68
return [
69
70
- 'operation-mode' => 'standard',
71
72
// redirection rules
73
'enable-redirection-to-converter' => true,
@@ -77,6 +77,7 @@ class Config
77
'do-not-pass-source-in-query-string' => false,
78
'redirect-to-existing-in-htaccess' => true,
79
'forward-query-string' => false,
80
81
// conversion options
82
'converters' => [],
@@ -95,6 +96,15 @@ class Config
95
'fail' => 'original',
96
'success-response' => 'converted',
97
98
// web service
99
'web-service' => [
100
'enabled' => false,
@@ -120,10 +130,10 @@ class Config
120
public static function applyOperationMode($config)
121
{
122
if (!isset($config['operation-mode'])) {
123
- $config['operation-mode'] = 'standard';
124
}
125
126
- if ($config['operation-mode'] == 'standard') {
127
$config = array_merge($config, [
128
'enable-redirection-to-converter' => true,
129
'only-redirect-to-converter-for-webp-enabled-browsers' => true,
@@ -133,13 +143,12 @@ class Config
133
'fail' => 'original',
134
'success-response' => 'converted',
135
]);
136
- } elseif ($config['operation-mode'] == 'just-convert') {
137
$config = array_merge($config, [
138
'only-redirect-to-converter-for-webp-enabled-browsers' => false,
139
'only-redirect-to-converter-on-cache-miss' => true,
140
'do-not-pass-source-in-query-string' => true,
141
'redirect-to-existing-in-htaccess' => false,
142
- 'destination-folder' => 'mingled',
143
'fail' => 'original',
144
'success-response' => 'original',
145
]);
@@ -150,19 +159,15 @@ class Config
150
$config = array_merge($config, [
151
'enable-redirection-to-converter' => false,
152
'destination-folder' => 'mingled',
153
]);
154
}
155
156
return $config;
157
}
158
159
- /**
160
- * Loads Config (if available), fills in the rest with defaults
161
- * also applies operation mode.
162
- */
163
- public static function loadConfigAndFix($checkQualityDetection = true)
164
{
165
- $config = Config::loadConfig();
166
if ($config === false) {
167
$config = self::getDefaultConfig(!$checkQualityDetection);
168
} else {
@@ -174,7 +179,10 @@ class Config
174
}
175
}
176
}
177
- $config = array_merge(self::getDefaultConfig(true), $config);
178
}
179
180
$config = self::applyOperationMode($config);
@@ -182,8 +190,8 @@ class Config
182
if (!isset($config['web-service'])) {
183
$config['web-service'] = [];
184
}
185
- if (!isset($config['web-service']['whitelist'])) {
186
- $config['web-service']['whitelist'] = [];
187
}
188
189
if ($config['converters'] == null) {
@@ -246,6 +254,15 @@ class Config
246
return $config;
247
}
248
249
250
public static $configForOptionsPage = null; // cache the result (called twice, - also in enqueue_scripts)
251
public static function getConfigForOptionsPage()
@@ -347,6 +364,43 @@ class Config
347
return self::loadJSONOptions(Paths::getWodOptionsFileName());
348
}
349
350
public static function saveConfigurationFile($config)
351
{
352
$config['paths-used-in-htaccess'] = [
@@ -359,7 +413,9 @@ class Config
359
$success = self::saveJSONOptions(Paths::getConfigFileName(), $config);
360
if ($success) {
361
State::setState('configured', true);
362
}
363
return $success;
364
}
365
return false;
@@ -376,6 +432,7 @@ class Config
376
$public = (isset($config['cache-control-public']) ? $config['cache-control-public'] : true);
377
$maxAge = (isset($config['cache-control-max-age']) ? $config['cache-control-max-age'] : $cacheControl);
378
$maxAgeOptions = [
379
'one-second' => 'max-age=1',
380
'one-minute' => 'max-age=60',
381
'one-hour' => 'max-age=3600',
@@ -439,12 +496,30 @@ class Config
439
unset($options['cache-control-custom']);
440
unset($options['cache-control-public']);
441
unset($options['cache-control-max-age']);
442
443
444
//unset($options['forward-query-string']); // It is used in webp-on-demand.php, so do not unset!
445
unset($options['do-not-pass-source-in-query-string']);
446
unset($options['redirect-to-existing-in-htaccess']);
447
448
return $options;
449
}
450
45
46
public static function saveJSONOptions($filename, $obj)
47
{
48
+ $result = @file_put_contents($filename, json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT));
49
/*if ($result === false) {
50
echo 'COULD NOT' . $filename;
51
}*/
67
68
return [
69
70
+ 'operation-mode' => 'varied-responses',
71
72
// redirection rules
73
'enable-redirection-to-converter' => true,
77
'do-not-pass-source-in-query-string' => false,
78
'redirect-to-existing-in-htaccess' => true,
79
'forward-query-string' => false,
80
+ 'enable-redirection-to-webp-realizer' => true,
81
82
// conversion options
83
'converters' => [],
96
'fail' => 'original',
97
'success-response' => 'converted',
98
99
+ // alter html options
100
+ 'alter-html' => [
101
+ 'enabled' => false,
102
+ 'replacement' => 'picture', // "picture" or "url"
103
+ 'hooks' => 'ob', // "content-hooks" or "ob"
104
+ 'only-for-webp-enabled-browsers' => false, // If true, there will be two HTML versions of each page
105
+ 'only-for-webps-that-exists' => false,
106
+ ],
107
+
108
// web service
109
'web-service' => [
110
'enabled' => false,
130
public static function applyOperationMode($config)
131
{
132
if (!isset($config['operation-mode'])) {
133
+ $config['operation-mode'] = 'varied-responses';
134
}
135
136
+ if ($config['operation-mode'] == 'varied-responses') {
137
$config = array_merge($config, [
138
'enable-redirection-to-converter' => true,
139
'only-redirect-to-converter-for-webp-enabled-browsers' => true,
143
'fail' => 'original',
144
'success-response' => 'converted',
145
]);
146
+ } elseif ($config['operation-mode'] == 'no-varied-responses') {
147
$config = array_merge($config, [
148
'only-redirect-to-converter-for-webp-enabled-browsers' => false,
149
'only-redirect-to-converter-on-cache-miss' => true,
150
'do-not-pass-source-in-query-string' => true,
151
'redirect-to-existing-in-htaccess' => false,
152
'fail' => 'original',
153
'success-response' => 'original',
154
]);
159
$config = array_merge($config, [
160
'enable-redirection-to-converter' => false,
161
'destination-folder' => 'mingled',
162
+ 'enable-redirection-to-webp-realizer' => false,
163
]);
164
}
165
166
return $config;
167
}
168
169
+ public static function fix($config, $checkQualityDetection = true)
170
{
171
if ($config === false) {
172
$config = self::getDefaultConfig(!$checkQualityDetection);
173
} else {
179
}
180
}
181
}
182
+ $defaultConfig = self::getDefaultConfig(true);
183
+ $config = array_merge($defaultConfig, $config);
184
+
185
+ $config['alter-html'] = array_replace_recursive($defaultConfig['alter-html'], $config['alter-html']);
186
}
187
188
$config = self::applyOperationMode($config);
190
if (!isset($config['web-service'])) {
191
$config['web-service'] = [];
192
}
193
+ if (($config['cache-control'] == 'set') && ($config['cache-control-max-age'] == '')) {
194
+ $config['cache-control-max-age'] = 'one-week';
195
}
196
197
if ($config['converters'] == null) {
254
return $config;
255
}
256
257
+ /**
258
+ * Loads Config (if available), fills in the rest with defaults
259
+ * also applies operation mode.
260
+ */
261
+ public static function loadConfigAndFix($checkQualityDetection = true)
262
+ {
263
+ return self::fix(Config::loadConfig(), $checkQualityDetection);
264
+ }
265
+
266
267
public static $configForOptionsPage = null; // cache the result (called twice, - also in enqueue_scripts)
268
public static function getConfigForOptionsPage()
364
return self::loadJSONOptions(Paths::getWodOptionsFileName());
365
}
366
367
+ /**
368
+ * Some of the options in config needs to be quickly accessible
369
+ * These are stored in wordpress autoloaded options
370
+ */
371
+ public static function updateAutoloadedOptions($config)
372
+ {
373
+ $config = self::fix($config, false);
374
+
375
+ update_option('webp-express-alter-html', $config['alter-html']['enabled'], true);
376
+ update_option('webp-express-alter-html-hooks', $config['alter-html']['hooks'], true);
377
+ update_option('webp-express-alter-html-replacement', $config['alter-html']['replacement'], true);
378
+
379
+ //update_option('webp-express-alter-html', $config['alter-html']['enabled'], true);
380
+
381
+ $obj = $config['alter-html'];
382
+ unset($obj['enabled']);
383
+ $obj['destination-folder'] = $config['destination-folder'];
384
+ $obj['destination-extension'] = $config['destination-extension'];
385
+ $obj['bases'] = [
386
+ 'uploads' => [
387
+ Paths::getUploadDirAbs(),
388
+ Paths::getUploadUrl()
389
+ ],
390
+ 'content' => [
391
+ Paths::getContentDirAbs(),
392
+ Paths::getContentUrl()
393
+ ],
394
+ ];
395
+ $obj['image-types'] = $config['image-types']; // 0=none,1=jpg, 2=png, 3=both
396
+
397
+ update_option(
398
+ 'webp-express-alter-html-options',
399
+ json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK),
400
+ true
401
+ );
402
+ }
403
+
404
public static function saveConfigurationFile($config)
405
{
406
$config['paths-used-in-htaccess'] = [
413
$success = self::saveJSONOptions(Paths::getConfigFileName(), $config);
414
if ($success) {
415
State::setState('configured', true);
416
+ self::updateAutoloadedOptions($config);
417
}
418
+
419
return $success;
420
}
421
return false;
432
$public = (isset($config['cache-control-public']) ? $config['cache-control-public'] : true);
433
$maxAge = (isset($config['cache-control-max-age']) ? $config['cache-control-max-age'] : $cacheControl);
434
$maxAgeOptions = [
435
+ '' => 'max-age=604800', // it has happened, but I don't think it can happen again...
436
'one-second' => 'max-age=1',
437
'one-minute' => 'max-age=60',
438
'one-hour' => 'max-age=3600',
496
unset($options['cache-control-custom']);
497
unset($options['cache-control-public']);
498
unset($options['cache-control-max-age']);
499
+ unset($options['paths-used-in-htaccess']);
500
+ unset($options['web-service']);
501
+ unset($options['alter-html']);
502
+ unset($options['enable-redirection-to-converter']);
503
+ unset($options['operation-mode']);
504
+ unset($options['only-redirect-to-converter-for-webp-enabled-browsers']);
505
+ unset($options['only-redirect-to-converter-on-cache-miss']);
506
+ unset($options['enable-redirection-to-webp-realizer']);
507
+
508
+
509
+ //unset($options['']);
510
+ //unset($options['']);
511
+ //unset($options['']);
512
+
513
514
515
//unset($options['forward-query-string']); // It is used in webp-on-demand.php, so do not unset!
516
unset($options['do-not-pass-source-in-query-string']);
517
unset($options['redirect-to-existing-in-htaccess']);
518
519
+ $options['paths'] = [
520
+ 'uploadDirRel' => Paths::getUploadDirRel()
521
+ ];
522
+
523
return $options;
524
}
525
lib/classes/FileHelper.php CHANGED
@@ -33,7 +33,7 @@ class FileHelper
33
* If failure, it returns $fallback
34
*/
35
public static function filePermWithFallback($filename, $fallback) {
36
- $perm = self::filePerm();
37
if ($perm === false) {
38
return $fallback;
39
}
@@ -45,7 +45,7 @@ class FileHelper
45
}
46
47
public static function humanReadableFilePermOfFile($filename) {
48
- return self::readableFilePerm(self::filePerm($filename));
49
}
50
51
/**
@@ -72,6 +72,56 @@ class FileHelper
72
return false;
73
}
74
75
/**
76
* Create a dir using same permissions as parent.
77
* If
33
* If failure, it returns $fallback
34
*/
35
public static function filePermWithFallback($filename, $fallback) {
36
+ $perm = self::filePerm($filename);
37
if ($perm === false) {
38
return $fallback;
39
}
45
}
46
47
public static function humanReadableFilePermOfFile($filename) {
48
+ return self::humanReadableFilePerm(self::filePerm($filename));
49
}
50
51
/**
72
return false;
73
}
74
75
+ public static function chmod_r($dir, $dirPerm = null, $filePerm = null, $uid = null, $gid = null, $regexFileMatchPattern = null, $regexDirMatchPattern = null) {
76
+ $fileIterator = new \FilesystemIterator($dir);
77
+
78
+ while ($fileIterator->valid()) {
79
+ $filename = $fileIterator->getFilename();
80
+ $filepath = $dir . "/" . $filename;
81
+
82
+ // echo $filepath . "\n";
83
+
84
+ $isDir = @is_dir($filepath);
85
+
86