WebP Express - Version 0.10.0

Version Description

  • Introduced "Operation modes" in order to keep setting screens simple but still allow tweaking
  • WebP Express can now be used in conjunction with Cache Enabler and ShortPixel
  • Cache-Control header is now added in .htaccess, when redirecting directly to existing webp

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

Download this release

Release Info

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

Code changes from version 0.9.1 to 0.10.0

Files changed (55) hide show
  1. BACKERS.md +13 -25
  2. README.md +168 -10
  3. README.txt +217 -26
  4. changelog.txt +12 -5
  5. lib/admin.php +1 -1
  6. lib/classes/CacheMover.php +153 -0
  7. lib/classes/Config.php +186 -69
  8. lib/classes/FileHelper.php +22 -0
  9. lib/classes/HTAccess.php +215 -94
  10. lib/classes/Paths.php +37 -10
  11. lib/migrate/migrate.php +12 -0
  12. lib/migrate/migrate4.php +68 -0
  13. lib/options/css/webp-express-options-page.css +37 -4
  14. lib/options/enqueue_scripts.php +23 -10
  15. lib/options/js/converters.js +1 -153
  16. lib/options/js/page.js +77 -21
  17. lib/options/options/cache-control.inc +0 -25
  18. lib/options/options/conversion-options/conversion-options.inc +28 -0
  19. lib/options/options/{converter-options → conversion-options/converter-options}/cwebp.php +0 -0
  20. lib/options/options/{converter-options → conversion-options/converter-options}/ewww.php +0 -0
  21. lib/options/options/{converter-options → conversion-options/converter-options}/gd.php +0 -0
  22. lib/options/options/{converter-options → conversion-options/converter-options}/imagick.php +0 -0
  23. lib/options/options/{converter-options → conversion-options/converter-options}/imagickbinary.php +0 -0
  24. lib/options/options/{converter-options → conversion-options/converter-options}/wpc.php +0 -0
  25. lib/options/options/{converters.inc → conversion-options/converters.inc} +0 -0
  26. lib/options/options/conversion-options/destination-extension.inc +15 -0
  27. lib/options/options/conversion-options/destination-folder.inc +15 -0
  28. lib/options/options/{metadata.inc → conversion-options/metadata.inc} +0 -0
  29. lib/options/options/{quality.inc → conversion-options/quality.inc} +0 -0
  30. lib/options/options/do-not-pass-source-path-in-query-string.inc +0 -6
  31. lib/options/options/image-types.inc +0 -22
  32. lib/options/options/operation-mode.inc +73 -0
  33. lib/options/options/redirect-to-existing.inc +0 -8
  34. lib/options/options/redirection-rules/add-vary-header-in-htaccess.inc +12 -0
  35. lib/options/options/redirection-rules/do-not-pass-source-path-in-query-string.inc +6 -0
  36. lib/options/options/redirection-rules/enable-redirection-to-converter.inc +18 -0
  37. lib/options/options/redirection-rules/image-types.inc +36 -0
  38. lib/options/options/redirection-rules/only-redirect-to-converter-for-webp-enabled-browsers.inc +13 -0
  39. lib/options/options/redirection-rules/only-redirect-to-converter-on-cache-miss.inc +8 -0
  40. lib/options/options/redirection-rules/redirect-to-existing.inc +11 -0
  41. lib/options/options/redirection-rules/redirection-rules.inc +33 -0
  42. lib/options/options/serve-options/cache-control.inc +115 -0
  43. lib/options/options/{response-on-failure.inc → serve-options/response-on-failure.inc} +0 -0
  44. lib/options/options/serve-options/response-on-success.inc +12 -0
  45. lib/options/options/serve-options/serve-options.inc +21 -0
  46. lib/options/options/web-service-options/web-service-options.inc +16 -0
  47. lib/options/options/{web-service.inc → web-service-options/web-service.inc} +1 -1
  48. lib/options/page-messages.php +1 -1
  49. lib/options/page.php +51 -66
  50. lib/options/submit.php +163 -160
  51. test/test.jpg.webp +0 -0
  52. test/test.webp +0 -0
  53. webp-express.php +1 -1
  54. wod/.webp +0 -0
  55. wod/webp-on-demand.php +87 -23
BACKERS.md CHANGED
@@ -1,38 +1,26 @@
1
- There are no backers yet. Become the first, by backing me up financially at patreon.com.
2
- Your name will then appear here.
3
4
- My page at patreon: https://www.patreon.com/rosell
5
6
7
- ## Generous backers via Patron
8
9
- There are no generous backers yet. [Be the first!](https://www.patreon.com/rosell)
10
11
- Generous backers will get their names listed here, along with a message - max 100 chars, and it may contain one link (the link url does not count any chars).
12
13
- Examples:
14
15
- | Name | Date | Message (70 chars limit, one link allowed) |
16
| --------------------- | ---------- | ----------------------------------------------------------------------- |
17
- | John Doe | 2018-11-23 | I have a similar plugin. [Check it out!](https://example.com/plugin)|
18
- | John Doe 2 | 2018-11-23 | I do in fact sell shoes - with WebP Express logo! [Check it out!]((https://printed-shoes.com/) |
19
-
20
- <small>
21
- I reserve the right to disallow inappropriate messages and links. No xxx sites or anything freaky or fishy, please. You may however advertise non-freaky-or-fishy things, if you wish. Just remember the audience. No point in trying to sell shoes here</small>
22
-
23
24
- ## Backers via Patron
25
-
26
- There are no backers yet. [Be the first!](https://www.patreon.com/rosell)
27
28
- Backers will get their names listed here, along with a message (max 70 chars, plain text only).
29
30
- Examples:
31
32
- | Name | Date | Message |
33
- | --------------------- | ---------- | ----------------------------------------------------------------------- |
34
- | John Doe | 2018-11-23 | Your children shouldn't just eat bananas. Buy some oranges too! |
35
- | John Doe 2 | 2018-11-23 | Perhaps you could work on multisite support? |
36
- | John Doe 3 | 2018-11-23 | Thank you. Your plugin changed my life! |
37
38
- <small>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</small>
1
2
+ # Backers
3
4
+ WebP Express is an MIT-licensed open source project. It is free and always will be. And I, the developer, promise not to try to harvest it later by creating a PRO version.
5
6
+ How is it financed then? Well, it isn't exactly. However, some people choose to support the development by buying the developer a cup of coffee, and some go even further, by becoming backers. Backers are nice folks making recurring monthly donations, and by doing this, they give me an excuse to put more work into the plugin than I really should.
7
8
+ To become a backer, yourself, [go to my page at patreon.com](https://www.patreon.com/rosell)
9
10
11
+ ## Backers via Patron
12
13
+ | Name | Date | Message (max 70 chars, plain text only) |
14
| --------------------- | ---------- | ----------------------------------------------------------------------- |
15
+ | Tammy Valgardson | 2018-12-27 | |
16
17
+ <sub>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</sub>
18
19
+ ## Generous backers via Patron
20
21
+ Generous backers will get their names listed here, along with a message - max 100 chars, and it may contain one link (the link url does not count any chars).
22
23
+ There are no generous backers yet. [Be the first!](https://www.patreon.com/rosell)
24
25
+ <sub>
26
+ I reserve the right to disallow inappropriate messages and links. No xxx sites or anything freaky or fishy, please. You may however advertise non-freaky-or-fishy things, if you wish. Just remember the audience. No point in trying to sell shoes here</sub>
README.md CHANGED
@@ -113,33 +113,69 @@ Easy enough. Browsers looks at the *content type* header rather than the URL to
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). For standard wordpress installations, the following rules should work:
117
118
- *Note that the rules stated here previously had a bug*: It had ” rather than ". The slightly slanted quotation mark does not work. Also, it used $request_uri, which contains the querystring, which resulted in errors when querystrings were supplied (ie ?debug)
119
-
120
- For 0.8.0:
121
```
122
if ($http_accept ~* "webp"){
123
- rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
124
}
125
```
126
- *Beware:* If you copy the code above, you might get an html-encoded ampersand before "wp-content"
127
128
- For 0.7.0:
129
```
130
if ($http_accept ~* "webp"){
131
- rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source=$request_filename&wp-content=wp-content break;
132
}
133
```
134
*Beware:* If you copy the code above, you might get an html-encoded ampersand before "wp-content"
135
136
The `wp-content` argument must point to the wp-content folder (relative to document root). In most installations, it is 'wp-content'.
137
138
Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
139
140
### I am on a WAMP stack
141
It has been reported that WebP Express *almost* works on WAMP stack (Windows, Apache, MySQL, PHP). I'd love to debug this, but do not own a Windows server or access to one... Can you help?
142
143
### Why do I not see the option to set WebP quality to auto?
144
The option will only display, if your system is able to detect jpeg qualities. To make your server capable to do that, install *Imagick extension* (PECL >= 2.2.2) or enable exec() calls and install either *Imagick* or *Gmagick*.
145
@@ -152,6 +188,128 @@ Chances are that the default setting of your CDN is not to forward any headers t
152
153
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.
154
155
### Does it work with lazy loaded images?
156
No plugins/frameworks has yet been discovered, which does not work with *WebP Express*.
157
@@ -164,7 +322,7 @@ The following lazy load plugins/frameworks has been tested and works with *WebP
164
### When is feature X coming? / Roadmap
165
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.
166
167
- Here are my loose plans ahead: The 0.10 release will probably add a some diagnose tool – this should release some time spend in the forum. I might also throw in configurable destination. 0.11 could be focused on PNG. 0.12 might be displaying rules for NGINX. 0.13 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.14 might be multisite support. 0.15 might be a file manager-like interface for inspecting generated webp files. 0.16 might be WAMP support. This is all guessing. I’m only planning one milestone at the time. You can follow the issue queue here: https://github.com/rosell-dk/webp-express/issues
168
169
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.
170
@@ -180,4 +338,4 @@ For more info, see the closed issues on the 0.9.0 milestone on the github reposi
180
Bread on the table don't come for free, even though this plugin does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to continue putting effort into this plugin:
181
182
- [Buy me a Coffee](https://ko-fi.com/rosell)
183
- - [Become a backer or sponsor on Patreon](https://www.patreon.com/rosell).
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"){
120
+ rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?wp-content=wp-content break;
121
}
122
```
123
124
+ If it doesn't work, try this instead:
125
+
126
```
127
if ($http_accept ~* "webp"){
128
+ rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
129
}
130
```
131
+
132
*Beware:* If you copy the code above, you might get an html-encoded ampersand before "wp-content"
133
134
The `wp-content` argument must point to the wp-content folder (relative to document root). In most installations, it is 'wp-content'.
135
136
+ Note that the rules above redirects every image request to the PHP script. To get better performance, you can add a rule that redirects jpeg/png requests directly to existing webp images. There are some rules for that [here](https://www.keycdn.com/blog/convert-to-webp-the-successor-of-jpeg#rewriterules-for-nginx-nginx-conf), but they need to be modified because WebP Express stores the webp files in a different location (`wp-content/webp-express/webp-images/doc-root`).
137
+
138
Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
139
140
### I am on a WAMP stack
141
It has been reported that WebP Express *almost* works on WAMP stack (Windows, Apache, MySQL, PHP). I'd love to debug this, but do not own a Windows server or access to one... Can you help?
142
143
+ ### I am using Jetpack
144
+ If you install Jetpack and enable the "Speed up image load times" then Jetpack will alter the HTML such that images are pointed to their CDN.
145
+
146
+ Ie:
147
+ `<img src="https://example.com/wp-content/uploads/2018/09/architecture.jpg">`
148
+
149
+ becomes:
150
+ `<img src="https://i0.wp.com/example.com/wp-content/uploads/2018/09/architecture.jpg">`
151
+
152
+ Jetpack automatically serves webp files to browsers that supports it using same mechanism as the standard WebP Express configuration: If the "Accept" header contains "image/webp", a webp is served (keeping original file extension, but setting the "content-type" header to "image/webp"), otherwise a jpg is served.
153
+
154
+ As images are no longer pointed to your original server, the .htaccess rules created by WebP Express will not have any effect.
155
+
156
+ So if you are using Jetpack you don't really need WebP Express?
157
+ Well, there is no point in having the "Speed up image load times" enabled together with WebP Express.
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.
165
+ - It optimizes jpegs and pngs as well (but note that only about 1 out of 5 users gets these, as webp is widely supported now)
166
+
167
+ Con Jetpack:
168
+ - It is a big plugin to install if you are only after the CDN
169
+ - It requires that you create an account on Wordpress.com
170
+
171
+ Pro WebP Express:
172
+ - You have control over quality and metadata
173
+ - It is a small plugin and care has been taken to add only very little overhead
174
+ - Plays well together with Cache Enabler. By not redirecting jpg to webp, there is no need to do any special configuration on the CDN and no issue with misleading file extension, if user downloads a file.
175
+
176
+ Con WebP Express:
177
+ - If you are using a CDN and you are redirecting jpg to webp, you must configure the CDN to forward the Accept header. It is not possible on all CDNs.
178
+
179
### Why do I not see the option to set WebP quality to auto?
180
The option will only display, if your system is able to detect jpeg qualities. To make your server capable to do that, install *Imagick extension* (PECL >= 2.2.2) or enable exec() calls and install either *Imagick* or *Gmagick*.
181
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.
195
+
196
+ However, the *Custom Cache Key* rule currently requires an *Enterprise* account. And if you already have that, you may as well go with the *Polish* feature, which starts at the “Pro” level plan. With the *Polish* feature, you will not need WebP Express.
197
+
198
+ To make *WebP Express* work on a free Cloudflare account, you have the following choices:
199
+
200
+ 1. You can configure the CDN not to cache jpeg images by adding the following page rule: If rule matches: `example.com/*.jpg`, set: *Cache level* to: *Bypass*
201
+
202
+ 2. You can set up another CDN (on another provider), which you just use for handling the images. You need to configure that CDN to forward the *Accept header*. You also need to install a Wordpress plugin that points images to that CDN.
203
+
204
+ 3. You can switch operation mode to "Just convert" and use either *Cache Enabler* or *ShortPixel* to modify the HTML. See the following FAQ items
205
+
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:
234
+ - *Also create WebP versions of the images, for free.*
235
+ - *Deliver the WebP versions of the images in the front-end*
236
+ - *Altering the page code, using the <PICTURE> tag syntax*
237
+ - As there is a limit to how many images you can convert freely with *ShortPixel*, you should disable the following options (also on the *Advanced* screen):
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.
251
+
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
287
+ ```
288
+
289
+ **flags:**
290
+ `-e robots=off` makes wget ignore rules in robots.txt
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*.
315
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
338
Bread on the table don't come for free, even though this plugin does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to continue putting effort into this plugin:
339
340
- [Buy me a Coffee](https://ko-fi.com/rosell)
341
+ - [Become a backer](https://github.com/rosell-dk/webp-express/blob/master/BACKERS.md)
README.txt CHANGED
@@ -4,7 +4,7 @@ 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.9.1
8
Requires PHP: 5.6
9
License: GPLv3
10
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -124,35 +124,69 @@ Easy enough. Browsers looks at the *content type* header rather than the URL to
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). For standard wordpress installations, the following rules should work:
128
129
- *Note that the rules stated here previously had a bug*: It had ” rather than ". The slightly slanted quotation mark does not work. Also, it used $request_uri, which contains the querystring, which resulted in errors when querystrings were supplied (ie ?debug)
130
-
131
- For 0.8.0:
132
- `
133
if ($http_accept ~* "webp"){
134
- rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
135
}
136
- `
137
- *Beware:* If you copy the code above, you might get an html-encoded ampersand before "wp-content"
138
139
- For 0.7.0:
140
- `
141
if ($http_accept ~* "webp"){
142
- rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source=$request_filename&wp-content=wp-content break;
143
}
144
- `
145
- *Beware:* If you copy the code above, you might get an html-encoded ampersand before "wp-content"
146
147
148
The `wp-content` argument must point to the wp-content folder (relative to document root). In most installations, it is 'wp-content'.
149
150
- Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
151
152
153
= I am on a WAMP stack =
154
It has been reported that WebP Express *almost* works on WAMP stack. I'd love to debug this, but do not own a Windows server or access to one... Can you help?
155
156
= Why do I not see the option to set WebP quality to auto? =
157
The option will only display, if your system is able to detect jpeg qualities. To make your server capable to do that, install *Imagick extension* (PECL >= 2.2.2) or enable exec() calls and install either *Imagick* or *Gmagick*.
158
@@ -160,15 +194,159 @@ If you have the *Imagick*, the *Imagick binary* or the *Remote WebP Express* con
160
161
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/).
162
163
- = How do I make this work with a CDN? =
164
- 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.
165
166
- 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:
167
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values#Values_for_an_image
168
- - so it is not an issue.
169
170
= Does it work with lazy loaded images? =
171
- No plugins/frameworks has yet been discovered, which does not work with *WebP Express*.
172
173
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.
174
@@ -176,14 +354,13 @@ The following lazy load plugins/frameworks has been tested and works with *WebP
176
- [BJ Lazy Load](https://da.wordpress.org/plugins/bj-lazy-load/)
177
- [Owl Carousel 2](https://owlcarousel2.github.io/OwlCarousel2/)
178
179
- = When is feature X coming? =
180
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.
181
182
- Here are my loose plans ahead: The 0.9 release will add redirect rule in .htaccess (optionally), perhaps also include configurable destination. 0.10 will probably be some diagnose tool – this should release some time spend in the forum. 0.11 could be focused on PNG. 0.12 might be displaying rules for NGINX. 0.13 might be supporting Save-Data header (send extra compressed images to clients who wants to use as little bandwidth as possible). 0.14 might be multisite support. 0.15 might be a file manager-like interface for inspecting generated webp files. 0.16 might be WAMP support. This is all guessing. I’m only planning one milestone at the time. You can follow the issue queue here: https://github.com/rosell-dk/webp-express/issues
183
184
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.
185
186
-
187
= How do I buy you a cup of coffee? =
188
Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacoff.ee/rosell).
189
@@ -193,10 +370,21 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
193
194
== Changelog ==
195
196
= 0.9.0 =
197
- * (released 27 dec 2018) *
198
* Optionally make .htaccess redirect directly to existing webp (improves performance)
199
- * Optionally do not send filename from .htaccess to the PHP in Querystring, but use other means (improves security and reduces risks of problems due to firewall rules)
200
* Fixed some bugs
201
202
For more info, see the closed issues on the 0.9.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.9.0
@@ -269,6 +457,9 @@ For older releases, check out changelog.txt
269
270
== Upgrade Notice ==
271
272
= 0.9.1 =
273
Fixed critical bug causing options page to go blank
274
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
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"){
131
+ rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?wp-content=wp-content break;
132
}
133
+ ```
134
135
+ If it doesn't work, try this instead:
136
+
137
+ ```
138
if ($http_accept ~* "webp"){
139
+ rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
140
}
141
+ ```
142
143
+ *Beware:* If you copy the code above, you might get an html-encoded ampersand before "wp-content"
144
145
The `wp-content` argument must point to the wp-content folder (relative to document root). In most installations, it is 'wp-content'.
146
147
+ Note that the rules above redirects every image request to the PHP script. To get better performance, you can add a rule that redirects jpeg/png requests directly to existing webp images. There are some rules for that [here](https://www.keycdn.com/blog/convert-to-webp-the-successor-of-jpeg#rewriterules-for-nginx-nginx-conf), but they need to be modified because WebP Express stores the webp files in a different location (`wp-content/webp-express/webp-images/doc-root`).
148
149
+ Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
150
151
= I am on a WAMP stack =
152
It has been reported that WebP Express *almost* works on WAMP stack. I'd love to debug this, but do not own a Windows server or access to one... Can you help?
153
154
+ = I am using Jetpack =
155
+ If you install Jetpack and enable the "Speed up image load times" then Jetpack will alter the HTML such that images are pointed to their CDN.
156
+
157
+ Ie:
158
+ `<img src="https://example.com/wp-content/uploads/2018/09/architecture.jpg">`
159
+
160
+ becomes:
161
+ `<img src="https://i0.wp.com/example.com/wp-content/uploads/2018/09/architecture.jpg">`
162
+
163
+ Jetpack automatically serves webp files to browsers that supports it using same mechanism as the standard WebP Express configuration: If the "Accept" header contains "image/webp", a webp is served (keeping original file extension, but setting the "content-type" header to "image/webp"), otherwise a jpg is served.
164
+
165
+ As images are no longer pointed to your original server, the .htaccess rules created by WebP Express will not have any effect.
166
+
167
+ So if you are using Jetpack you don't really need WebP Express?
168
+ Well, there is no point in having the "Speed up image load times" enabled together with WebP Express.
169
+
170
+ But there is a case for using WebP Express rather than Jetpacks "Speed up image load times" feature:
171
+
172
+ Jetpack has the same drawback as the *standard* WebP Express configuration: 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.
173
+
174
+ Pro Jetpack:
175
+ - It is a free CDN which serves webp out of the box.
176
+ - It optimizes jpegs and pngs as well (but note that only about 1 out of 5 users gets these, as webp is widely supported now)
177
+
178
+ Con Jetpack:
179
+ - It is a big plugin to install if you are only after the CDN
180
+ - It requires that you create an account on Wordpress.com
181
+
182
+ Pro WebP Express:
183
+ - You have control over quality and metadata
184
+ - It is a small plugin and care has been taken to add only very little overhead
185
+ - Plays well together with Cache Enabler. By not redirecting jpg to webp, there is no need to do any special configuration on the CDN and no issue with misleading file extension, if user downloads a file.
186
+
187
+ Con WebP Express:
188
+ - If you are using a CDN and you are redirecting jpg to webp, you must configure the CDN to forward the Accept header. It is not possible on all CDNs.
189
+
190
= Why do I not see the option to set WebP quality to auto? =
191
The option will only display, if your system is able to detect jpeg qualities. To make your server capable to do that, install *Imagick extension* (PECL >= 2.2.2) or enable exec() calls and install either *Imagick* or *Gmagick*.
192
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.
229
+
230
+ However, the *Custom Cache Key* rule currently requires an *Enterprise* account. And if you already have that, you may as well go with the *Polish* feature, which starts at the “Pro” level plan. With the *Polish* feature, you will not need WebP Express.
231
+
232
+ To make *WebP Express* work on a free Cloudflare account, you have the following choices:
233
+
234
+ 1. You can configure the CDN not to cache jpeg images by adding the following page rule: If rule matches: `example.com/*.jpg`, set: *Cache level* to: *Bypass*
235
236
+ 2. You can set up another CDN (on another provider), which you just use for handling the images. You need to configure that CDN to forward the *Accept header*. You also need to install a Wordpress plugin that points images to that CDN.
237
+
238
+ 3. You can switch operation mode to "Just convert" and use either Cache Enabler or Shortpixel to modify the HTML. See the follwoning FAQ items
239
+
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
+
264
+ *2. Setup ShortPixel*
265
+ - Install [ShortPixel](https://wordpress.org/plugins/shortpixel-image-optimiser/) the usual way
266
+ - Get an API key and enter it on the options page.
267
+ - In *Advanced*, enable the following options:
268
+ - *Also create WebP versions of the images, for free.*
269
+ - *Deliver the WebP versions of the images in the front-end*
270
+ - *Altering the page code, using the <PICTURE> tag syntax*
271
+ - As there is a limit to how many images you can convert freely with *ShortPixel*, you should disable the following options (also on the *Advanced* screen):
272
+ - *Automatically optimize images added by users in front end.*
273
+ - *Automatically optimize Media Library items after they are uploaded (recommended).*
274
+
275
+ *3. Visit a page*
276
+ 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.
277
+
278
+ *4. Visit the page again*
279
+ 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.
280
+
281
+ *ShortPixel or Cache Enabler ?*
282
+ 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.
283
+
284
+ 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.
285
+
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
322
+ ```
323
+
324
+ **flags:**
325
+ `-e robots=off` makes wget ignore rules in robots.txt
326
+ `-np` (no-parent) makes wget stay within the boundaries (doesn't go into parent folders)
327
+ `w 2` Waits two seconds between each request, in order not to stress the server
328
+
329
+ *4. Clear the Cache Enabler cache.*
330
+ Click the "Clear Cache" button in the top right corner in order to clear the Cache Enabler cache.
331
+
332
+ *5. Inspect the HTML*
333
+ When visiting a page with images on, different HTML will be served to browsers, depending on whether they support webp or not.
334
+
335
+ 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">`
336
+
337
+
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
- [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
364
= How do I buy you a cup of coffee? =
365
Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacoff.ee/rosell).
366
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
376
+ * Cache-Control header is now added in *.htaccess*, when redirecting directly to existing webp
377
+
378
+ For more info, see the closed issues on the 0.10.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/7?closed=1
379
+
380
+ = 0.9.1 =
381
+ *(released 28 dec 2018)*
382
+ * Fixed critical bug causing blank page on options page
383
+
384
= 0.9.0 =
385
+ *(released 27 dec 2018)*
386
* Optionally make .htaccess redirect directly to existing webp (improves performance)
387
+ * Optionally do not send filename from *.htaccess* to the PHP in Querystring, but use other means (improves security and reduces risks of problems due to firewall rules)
388
* Fixed some bugs
389
390
For more info, see the closed issues on the 0.9.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.9.0
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
+
463
= 0.9.1 =
464
Fixed critical bug causing options page to go blank
465
changelog.txt CHANGED
@@ -1,21 +1,28 @@
1
= 0.9.1 =
2
- (released 28 dec 2018)
3
* Fixed critical bug causing blank page on options page
4
5
= 0.9.0 =
6
- (released 27 dec 2018)
7
* Optionally make .htaccess redirect directly to existing webp (improves performance)
8
- * Optionally do not send filename from .htaccess to the PHP in Querystring, but use other means (improves security and reduces risks of problems due to firewall rules)
9
* Fixed some bugs
10
11
For more info, see the closed issues on the 0.9.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.9.0
12
13
= 0.8.1 =
14
- * (released 11 dec 2018) *
15
* Fixed javascript bug
16
17
= 0.8.0 =
18
- * (released 11 dec 2018) *
19
* New conversion method, which calls imagick binary directly. This will make WebP express work out of the box on more systems
20
* Made sure not to trigger LFI warning i Wordfence (to activate, click the force .htaccess button)
21
* Imagick can now be configured to set quality to auto on systems where the auto option isn't generally available
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
4
+ * Cache-Control header is now added in *.htaccess*, when redirecting directly to existing webp
5
+
6
+ For more info, see the closed issues on the 0.10.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/7?closed=1
7
+
8
= 0.9.1 =
9
+ *(released 28 dec 2018)*
10
* Fixed critical bug causing blank page on options page
11
12
= 0.9.0 =
13
+ *(released 27 dec 2018)*
14
* Optionally make .htaccess redirect directly to existing webp (improves performance)
15
+ * Optionally do not send filename from *.htaccess* to the PHP in Querystring, but use other means (improves security and reduces risks of problems due to firewall rules)
16
* Fixed some bugs
17
18
For more info, see the closed issues on the 0.9.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.9.0
19
20
= 0.8.1 =
21
+ *(released 11 dec 2018)*
22
* Fixed javascript bug
23
24
= 0.8.0 =
25
+ *(released 11 dec 2018)*
26
* New conversion method, which calls imagick binary directly. This will make WebP express work out of the box on more systems
27
* Made sure not to trigger LFI warning i Wordfence (to activate, click the force .htaccess button)
28
* Imagick can now be configured to set quality to auto on systems where the auto option isn't generally available
lib/admin.php CHANGED
@@ -2,7 +2,7 @@
2
use \WebPExpress\State;
3
4
// When an update requires a migration, the number should be increased
5
- define('WEBPEXPRESS_MIGRATION_VERSION', '3');
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', '4');
6
7
if (WEBPEXPRESS_MIGRATION_VERSION != get_option('webp-express-migration-version', 0)) {
8
// run migration logic
lib/classes/CacheMover.php ADDED
@@ -0,0 +1,153 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "FileHelper.php";
6
+ use \WebPExpress\FileHelper;
7
+
8
+ include_once "Paths.php";
9
+ use \WebPExpress\Paths;
10
+
11
+
12
+ class CacheMover
13
+ {
14
+
15
+ public static function getUploadFolder($destinationFolder)
16
+ {
17
+ switch ($destinationFolder) {
18
+ case 'mingled':
19
+ return Paths::getUploadDirAbs();
20
+ case 'separate':
21
+ return Paths::getCacheDirAbs() . '/doc-root/' . Paths::getUploadDirRel();
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Move cache because of change in options.
27
+ * Only move the upload folder
28
+ * Only move those that has an original
29
+ * Only move those that can be moved.
30
+ * @return [$numFilesMoved, $numFilesFailedMoving]
31
+ */
32
+ public static function move($newConfig, $oldConfig)
33
+ {
34
+ $fromDir = self::getUploadFolder($oldConfig['destination-folder']);
35
+ $fromExt = $oldConfig['destination-extension'];
36
+
37
+ $toDir = self::getUploadFolder($newConfig['destination-folder']);
38
+ $toExt = $newConfig['destination-extension'];
39
+
40
+ $srcDir = self::getUploadFolder('mingled');
41
+
42
+ // for testing!
43
+ /*
44
+ $fromDir = self::getUploadFolder('mingled'); // separate | mingled
45
+ $toDir = self::getUploadFolder('mingled');
46
+ $fromExt = 'set'; // set | append
47
+ $toExt = 'append';
48
+
49
+ echo '<pre>';
50
+ echo 'from: ' . $fromDir . '<br>';
51
+ echo 'to: ' . $toDir . '<br>';
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
+
59
+ /**
60
+ * @return [$numFilesMoved, $numFilesFailedMoving]
61
+ */
62
+ public static function moveRecursively($fromDir, $toDir, $srcDir, $fromExt, $toExt)
63
+ {
64
+ if (!@is_dir($fromDir)) {
65
+ return [0, 0];
66
+ }
67
+ if (!@file_exists($toDir)) {
68
+ if (!@mkdir($toDir)) {
69
+ return [0, 0];
70
+ }
71
+ }
72
+
73
+ $numFilesMoved = 0;
74
+ $numFilesFailedMoving = 0;
75
+
76
+ //$filenames = @scandir($fromDir);
77
+ $fileIterator = new \FilesystemIterator($fromDir);
78
+
79
+ //foreach ($filenames as $filename) {
80
+ while ($fileIterator->valid()) {
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
+
91
+ // Remove dir, if its empty. But do not remove dirs in srcDir
92
+ if ($fromDir != $srcDir) {
93
+ $fileIterator2 = new \FilesystemIterator($fromDir . "/" . $filename);
94
+ $dirEmpty = !$fileIterator2->valid();
95
+ if ($dirEmpty) {
96
+ @rmdir($fromDir . "/" . $filename);
97
+ }
98
+ }
99
+ } else {
100
+ // its a file.
101
+ // check if its a webp
102
+ if (strpos($filename, '.webp', strlen($filename) - 5) !== false) {
103
+
104
+ $filenameWithoutWebp = substr($filename, 0, strlen($filename) - 5);
105
+ $srcFilePathWithoutWebp = $srcDir . "/" . $filenameWithoutWebp;
106
+
107
+ // check if a corresponding source file exists
108
+ $newFilename = null;
109
+ if (($fromExt == 'append') && (@file_exists($srcFilePathWithoutWebp))) {
110
+ if ($toExt == 'append') {
111
+ $newFilename = $filename;
112
+ } else {
113
+ // remove ".jpg" part of filename (or ".png")
114
+ $newFilename = preg_replace("/\.(jpe?g|png)\.webp#x2F;", '.webp', $filename);
115
+ }
116
+ } elseif ($fromExt == 'set') {
117
+ if ($toExt == 'set') {
118
+ if (
119
+ @file_exists($srcFilePathWithoutWebp . ".jpg") ||
120
+ @file_exists($srcFilePathWithoutWebp . ".jpeg") ||
121
+ @file_exists($srcFilePathWithoutWebp . ".png")
122
+ ) {
123
+ $newFilename = $filename;
124
+ }
125
+ } else {
126
+ // append
127
+ if (@file_exists($srcFilePathWithoutWebp . ".jpg")) {
128
+ $newFilename = $filenameWithoutWebp . ".jpg.webp";
129
+ } elseif (@file_exists($srcFilePathWithoutWebp . ".jpeg")) {
130
+ $newFilename = $filenameWithoutWebp . ".jpeg.webp";
131
+ } elseif (@file_exists($srcFilePathWithoutWebp . ".png")) {
132
+ $newFilename = $filenameWithoutWebp . ".png.webp";
133
+ }
134
+ }
135
+ }
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++;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ $fileIterator->next();
149
+ }
150
+ return [$numFilesMoved, $numFilesFailedMoving];
151
+ }
152
+
153
+ }
lib/classes/Config.php CHANGED
@@ -58,34 +58,44 @@ class Config
58
return self::loadJSONOptions(Paths::getConfigFileName());
59
}
60
61
- public static $configForOptionsPage = null; // cache the result (called twice, - also in enqueue_scripts)
62
- public static function getConfigForOptionsPage()
63
- {
64
- if (isset(self::$configForOptionsPage)) {
65
- return self::$configForOptionsPage;
66
- }
67
- // Test converters
68
- $testResult = TestRun::getConverterStatus();
69
- $workingConverters = [];
70
- if ($testResult) {
71
- $workingConverters = $testResult['workingConverters'];
72
- //print_r($testResult);
73
}
74
75
- $canDetectQuality = TestRun::isLocalQualityDetectionWorking();
76
- $defaultConfig = [
77
- 'cache-control' => 'no-header',
78
- 'cache-control-custom' => 'public, max-age:3600',
79
- 'converters' => [],
80
- 'fail' => 'original',
81
- 'forward-query-string' => true,
82
'image-types' => 1,
83
- 'quality-auto' => $canDetectQuality,
84
'max-quality' => 80,
85
'quality-specific' => 70,
86
'metadata' => 'none',
87
- 'do-not-pass-source-in-query-string' => false,
88
- 'redirect-to-existing-in-htaccess' => false,
89
'web-service' => [
90
'enabled' => false,
91
'whitelist' => [
@@ -101,20 +111,74 @@ class Config
101
102
]
103
];
104
105
- $defaultConverters = ConvertersHelper::$defaultConverters;
106
107
- $config = self::loadConfig();
108
- //echo '<pre>' . print_r($config, true) . '</pre>';
109
- if (!$config) {
110
- $config = [];
111
}
112
- //$config = [];
113
114
- $config = array_merge($defaultConfig, $config);
115
- if ($config['converters'] == null) {
116
- $config['converters'] = [];
117
}
118
if (!isset($config['web-service'])) {
119
$config['web-service'] = [];
120
}
@@ -122,25 +186,30 @@ class Config
122
$config['web-service']['whitelist'] = [];
123
}
124
125
- // Remove keys in whitelist (so they cannot easily be picked up by examining the html)
126
- foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
127
- unset($whitelistEntry['api-key']);
128
}
129
130
- // Remove keys from WPC converters
131
- foreach ($config['converters'] as &$converter) {
132
- if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
133
- if (isset($converter['options']['api-key'])) {
134
- if ($converter['options']['api-key'] != '') {
135
- $converter['options']['_api-key-non-empty'] = true;
136
- }
137
- unset($converter['options']['api-key']);
138
- }
139
- }
140
- }
141
142
- if (count($config['converters']) == 0) {
143
// This is first time visit!
144
145
if (count($workingConverters) == 0) {
146
// No converters are working
@@ -172,15 +241,45 @@ class Config
172
}
173
$config['converters'] = array_merge($resultPart1, $resultPart2);
174
}
175
176
- // $workingConverters
177
- //echo '<pre>' . print_r($converters, true) . '</pre>';
178
- } else {
179
- // not first time visit...
180
- // merge missing converters in
181
- $config['converters'] = ConvertersHelper::mergeConverters($config['converters'], ConvertersHelper::$defaultConverters);
182
}
183
184
185
// Set "working" and "error" properties
186
if ($testResult) {
@@ -189,7 +288,12 @@ class Config
189
$hasError = isset($testResult['errors'][$converterId]);
190
$working = !$hasError;
191
192
if (isset($converter['working']) && ($converter['working'] != $working)) {
193
if ($working) {
194
Messenger::printMessage(
195
'info',
@@ -202,6 +306,7 @@ class Config
202
);
203
}
204
}
205
$converter['working'] = $working;
206
if ($hasError) {
207
$error = $testResult['errors'][$converterId];
@@ -260,6 +365,30 @@ class Config
260
return false;
261
}
262
263
public static function generateWodOptionsFromConfigObj($config)
264
{
265
$options = $config;
@@ -291,23 +420,7 @@ class Config
291
}
292
293
if (isset($options['cache-control'])) {
294
- $cacheControl = $options['cache-control'];
295
- $cacheControlOptions = [
296
- 'no-header' => '',
297
- 'one-second' => 'public, max-age=1',
298
- 'one-minute' => 'public, max-age=60',
299
- 'one-hour' => 'public, max-age=3600',
300
- 'one-day' => 'public, max-age=86400',
301
- 'one-week' => 'public, max-age=604800',
302
- 'one-month' => 'public, max-age=2592000',
303
- 'one-year' => 'public, max-age=31536000',
304
- ];
305
-
306
- if (isset($cacheControlOptions[$cacheControl])) {
307
- $options['cache-control-header'] = $cacheControlOptions[$cacheControl];
308
- } else {
309
- $options['cache-control-header'] = $options['cache-control-custom'];
310
- }
311
}
312
313
$auto = (isset($options['quality-auto']) && $options['quality-auto']);
@@ -324,6 +437,10 @@ class Config
324
unset($options['image-types']);
325
unset($options['cache-control']);
326
unset($options['cache-control-custom']);
327
//unset($options['forward-query-string']); // It is used in webp-on-demand.php, so do not unset!
328
unset($options['do-not-pass-source-in-query-string']);
329
unset($options['redirect-to-existing-in-htaccess']);
58
return self::loadJSONOptions(Paths::getConfigFileName());
59
}
60
61
+ public static function getDefaultConfig($skipQualityAuto = false) {
62
+ if ($skipQualityAuto) {
63
+ $qualityAuto = null;
64
+ } else {
65
+ $qualityAuto = TestRun::isLocalQualityDetectionWorking();
66
}
67
68
+ return [
69
+
70
+ 'operation-mode' => 'standard',
71
+
72
+ // redirection rules
73
+ 'enable-redirection-to-converter' => true,
74
'image-types' => 1,
75
+ 'only-redirect-to-converter-on-cache-miss' => false,
76
+ 'only-redirect-to-converter-for-webp-enabled-browsers' => true,
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' => [],
83
+ 'quality-auto' => $qualityAuto,
84
'max-quality' => 80,
85
'quality-specific' => 70,
86
'metadata' => 'none',
87
+ 'destination-folder' => 'separate',
88
+ 'destination-extension' => 'append',
89
+
90
+ // serve options
91
+ 'cache-control' => 'no-header', /* can be "no-header", "set" or "custom" */
92
+ 'cache-control-custom' => 'public, max-age:86400, stale-while-revalidate=604800, stale-if-error=604800',
93
+ 'cache-control-max-age' => 'one-week',
94
+ 'cache-control-public' => false,
95
+ 'fail' => 'original',
96
+ 'success-response' => 'converted',
97
+
98
+ // web service
99
'web-service' => [
100
'enabled' => false,
101
'whitelist' => [
111
112
]
113
];
114
+ }
115
116
+ /**
117
+ * Apply operation mode (set the hidden defaults that comes along with the mode)
118
+ * @return An altered configuration array
119
+ */
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,
130
+ 'only-redirect-to-converter-on-cache-miss' => false,
131
+ 'do-not-pass-source-in-query-string' => true,
132
+ //'redirect-to-existing-in-htaccess' => true,
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
+ ]);
146
+ } elseif ($config['operation-mode'] == 'just-redirect') {
147
+
148
+ // TODO: Go through these...
149
+
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 {
169
+ if ($checkQualityDetection) {
170
+ if (isset($config['quality-auto']) && ($config['quality-auto'])) {
171
+ $qualityDetectionWorking = TestRun::isLocalQualityDetectionWorking();
172
+ if (!TestRun::isLocalQualityDetectionWorking()) {
173
+ $config['quality-auto'] = false;
174
+ }
175
+ }
176
+ }
177
+ $config = array_merge(self::getDefaultConfig(true), $config);
178
}
179
+
180
+ $config = self::applyOperationMode($config);
181
+
182
if (!isset($config['web-service'])) {
183
$config['web-service'] = [];
184
}
186
$config['web-service']['whitelist'] = [];
187
}
188
189
+ if ($config['converters'] == null) {
190
+ $config['converters'] = [];
191
}
192
193
+ if (count($config['converters']) > 0) {
194
+ // merge missing converters in
195
+ $config['converters'] = ConvertersHelper::mergeConverters(
196
+ $config['converters'],
197
+ ConvertersHelper::$defaultConverters
198
+ );
199
+ } else {
200
201
// This is first time visit!
202
+ // We must add converters.
203
+ // We want to order them according to which ones that are working,
204
+ // so we run
205
+ $testResult = TestRun::getConverterStatus();
206
+ $workingConverters = [];
207
+ if ($testResult) {
208
+ $workingConverters = $testResult['workingConverters'];
209
+ //print_r($testResult);
210
+ }
211
+
212
+ $defaultConverters = ConvertersHelper::$defaultConverters;
213
214
if (count($workingConverters) == 0) {
215
// No converters are working
241
}
242
$config['converters'] = array_merge($resultPart1, $resultPart2);
243
}
244
+ }
245
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()
252
+ {
253
+ if (isset(self::$configForOptionsPage)) {
254
+ return self::$configForOptionsPage;
255
+ }
256
+
257
+ // Test converters
258
+ $testResult = TestRun::getConverterStatus();
259
+ $workingConverters = [];
260
+ if ($testResult) {
261
+ $workingConverters = $testResult['workingConverters'];
262
+ //print_r($testResult);
263
}
264
265
+ $config = self::loadConfigAndFix();
266
+
267
+ // Remove keys in whitelist (so they cannot easily be picked up by examining the html)
268
+ foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
269
+ unset($whitelistEntry['api-key']);
270
+ }
271
+
272
+ // Remove keys from WPC converters
273
+ foreach ($config['converters'] as &$converter) {
274
+ if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
275
+ if (isset($converter['options']['api-key'])) {
276
+ if ($converter['options']['api-key'] != '') {
277
+ $converter['options']['_api-key-non-empty'] = true;
278
+ }
279
+ unset($converter['options']['api-key']);
280
+ }
281
+ }
282
+ }
283
284
// Set "working" and "error" properties
285
if ($testResult) {
288
$hasError = isset($testResult['errors'][$converterId]);
289
$working = !$hasError;
290
291
+ /*
292
+ Don't print this stuff here. It can end up in the head tag.
293
+ TODO: Move it somewhere
294
if (isset($converter['working']) && ($converter['working'] != $working)) {
295
+
296
+ // TODO: webpexpress_converterName($converterId)
297
if ($working) {
298
Messenger::printMessage(
299
'info',
306
);
307
}
308
}
309
+ */
310
$converter['working'] = $working;
311
if ($hasError) {
312
$error = $testResult['errors'][$converterId];
365
return false;
366
}
367
368
+ public static function getCacheControlHeader($config) {
369
+ $cacheControl = $config['cache-control'];
370
+ switch ($cacheControl) {
371
+ case 'custom':
372
+ return $config['cache-control-custom'];
373
+ case 'no-header':
374
+ return '';
375
+ default:
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',
382
+ 'one-day' => 'max-age=86400',
383
+ 'one-week' => 'max-age=604800',
384
+ 'one-month' => 'max-age=2592000',
385
+ 'one-year' => 'max-age=31536000',
386
+ ];
387
+ return ($public ? 'public, ' : 'private, ') . $maxAgeOptions[$maxAge];
388
+ }
389
+
390
+ }
391
+
392
public static function generateWodOptionsFromConfigObj($config)
393
{
394
$options = $config;
420
}
421
422
if (isset($options['cache-control'])) {
423
+ $options['cache-control-header'] = self::getCacheControlHeader($config);
424
}
425
426
$auto = (isset($options['quality-auto']) && $options['quality-auto']);
437
unset($options['image-types']);
438
unset($options['cache-control']);
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']);
lib/classes/FileHelper.php CHANGED
@@ -27,6 +27,19 @@ class FileHelper
27
return octdec(substr(decoct($perm), -4));
28
}
29
30
public static function humanReadableFilePerm($mode) {
31
return substr(decoct($mode), -4);
32
}
@@ -59,6 +72,15 @@ class FileHelper
59
return false;
60
}
61
62
/**
63
* Get directory part of filename.
64
* Ie '/var/www/.htaccess' => '/var/www'
27
return octdec(substr(decoct($perm), -4));
28
}
29
30
+
31
+ /**
32
+ * Get file permission of a file (integer). Only get the last part, ie 0644
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
+ }
40
+ return $perm;
41
+ }
42
+
43
public static function humanReadableFilePerm($mode) {
44
return substr(decoct($mode), -4);
45
}
72
return false;
73
}
74
75
+ /**
76
+ * Create a dir using same permissions as parent.
77
+ * If
78
+ */
79
+ /*
80
+ public static function mkdirSamePermissionsAsParent($pathname) {
81
+
82
+ }*/
83
+
84
/**
85
* Get directory part of filename.
86
* Ie '/var/www/.htaccess' => '/var/www'
lib/classes/HTAccess.php CHANGED
@@ -16,9 +16,30 @@ use \WebPExpress\State;
16
17
class HTAccess
18
{
19
- // (called from this file only)
20
public static function generateHTAccessRulesFromConfigObj($config, $htaccessDir = 'index')
21
{
22
/* Calculate $fileExt */
23
$imageTypes = $config['image-types'];
24
$fileExtensions = [];
@@ -31,47 +52,44 @@ class HTAccess
31
$fileExt = implode('|', $fileExtensions);
32
33
if ($imageTypes == 0) {
34
- return '# WebP Express disabled (no image types have been choosen to be converted)';
35
}
36
37
/* Build rules */
38
$rules = '';
39
40
// The next line sets an environment variable.
41
// On the options page, we verify if this is set to diagnose if "AllowOverride None" is presented in 'httpd.conf'
42
//$rules .= "# The following SetEnv allows to diagnose if .htaccess files are turned off\n";
43
//$rules .= "SetEnv HTACCESS on\n\n";
44
45
$rules .= "<IfModule mod_rewrite.c>\n" .
46
" RewriteEngine On\n\n";
47
48
- //$pathToExisting = Paths::getPathToExisting();
49
- //$pathToExisting = Paths::getCacheDirRel() . '/doc-root/' . Paths::getHomeDirRel();
50
- //$pathToExisting = Paths::getCacheDirRel() . '/doc-root/' . Paths::getPluginDirRel();
51
- //$pathToExisting = Paths::getCacheDirRel() . '/doc-root/' . Paths::getPluginDirRel();
52
- $pathToExisting = Paths::getCacheDirRel() . '/doc-root/';
53
switch ($htaccessDir) {
54
case 'index':
55
- $pathToExisting .= Paths::getIndexDirRel();
56
break;
57
case 'home':
58
- $pathToExisting .= Paths::getHomeDirRel();
59
break;
60
case 'plugin':
61
- $pathToExisting .= Paths::getPluginDirRel();
62
break;
63
case 'uploads':
64
- $pathToExisting .= Paths::getUploadDirRel();
65
break;
66
case 'wp-content':
67
- $pathToExisting .= Paths::getWPContentDirRel();
68
break;
69
}
70
71
- $doNotpassSourceInQS = (isset($config['do-not-pass-source-in-query-string']) && ($config['do-not-pass-source-in-query-string']));
72
- $passSourceInQS = !$doNotpassSourceInQS;
73
-
74
- $redirectToExisting = (isset($config['redirect-to-existing-in-htaccess']) && ($config['redirect-to-existing-in-htaccess']));
75
76
// TODO: Is it possible to handle when wp-content is outside document root?
77
@@ -81,65 +99,171 @@ class HTAccess
81
// to get: RewriteRule ^\/?(.*)\.(jpe?g)$ /wp-content-moved/webp-express/webp-images/doc-root/plugins-moved/$1.$2.webp [NC,T=image/webp,QSD,E=WEBPACCEPT:1,E=EXISTING:1,L]
82
83
// https://stackoverflow.com/questions/34124819/mod-rewrite-set-custom-header-through-htaccess
84
- if ($redirectToExisting) {
85
$rules .= " # Redirect to existing converted image in cache-dir (if browser supports webp)\n";
86
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
87
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
88
- $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $pathToExisting . "/$1.$2.webp -f\n";
89
- $rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $pathToExisting . "/$1.$2.webp [NC,T=image/webp,QSD,E=EXISTING:1,L]\n\n";
90
- }
91
-
92
- if (!$passSourceInQS) {
93
- $rules .= " # Pass REQUEST_FILENAME to PHP by setting request header\n" .
94
- " RewriteCond %{HTTP_ACCEPT} image/webp\n" .
95
- " RewriteCond %{REQUEST_FILENAME} -f\n" .
96
- " RewriteRule ^(.*)\.(jpe?g)$ - [E=REQFN:%{REQUEST_FILENAME}]\n" .
97
- " <IfModule mod_headers.c>\n" .
98
- " RequestHeader set REQFN \"%{REQFN}e\" env=REQFN\n" .
99
- " </IfModule>\n\n";
100
- }
101
- $rules .= " # Redirect images to webp-on-demand.php (if browser supports webp)\n";
102
- $rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
103
- $rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
104
- if ($config['forward-query-string']) {
105
- $rules .= " RewriteCond %{QUERY_STRING} (.*)\n";
106
- }
107
-
108
- // TODO:
109
- // Add "NE" flag?
110
- // https://github.com/rosell-dk/webp-convert/issues/95
111
- // (and try testing spaces in directory paths)
112
- $rules .= " RewriteRule ^(.*)\.(" . $fileExt . ")$ " .
113
- "/" . Paths::getWodUrlPath() .
114
- ($passSourceInQS ? "?xsource=x%{SCRIPT_FILENAME}&" : "?") .
115
- "wp-content=" . Paths::getWPContentDirRel() .
116
- ($config['forward-query-string'] ? '&%1' : '') .
117
- " [NC,L]\n"; // E=WOD:1
118
-
119
-
120
- // Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
121
- $rules .="\n <IfModule mod_headers.c>\n" .
122
- " # Set Vary:Accept header for the image types handled by WebP Express.\n" .
123
- " # The purpose is to make CDN cache both original images and converted images.\n" .
124
- " SetEnvIf Request_URI \"\.(" . $fileExt . ")\" ADDVARY\n" .
125
- " Header append \"Vary\" \"Accept\" env=ADDVARY\n\n" .
126
- " # Set X-WebP-Express header for diagnose purposes\n" .
127
- " # Apache appends \"REDIRECT_\" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not.\n" .
128
- " # So, the next line is for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
129
- " SetEnvIf REDIRECT_EXISTING 1 EXISTING=1\n" .
130
- //" SetEnvIf REDIRECT_WOD 1 WOD=1\n\n" .
131
- //" # Set the debug header\n" .
132
- " Header set \"X-WebP-Express\" \"Redirected directly to existing webp\" env=EXISTING\n" .
133
- //" Header set \"X-WebP-Express\" \"Redirected to image converter\" env=WOD\n" .
134
- " </IfModule>\n\n";
135
136
- $rules .="</IfModule>\n";
137
138
139
140
141
142
- /*if ($redirectToExisting) {
143
$rules .=
144
"<IfModule mod_headers.c>\n" .
145
" # Append Vary Accept header, when the rules above are redirecting to existing webp\n" .
@@ -156,8 +280,9 @@ class HTAccess
156
"</IfModule>\n\n";
157
}*/
158
159
- $rules .= "AddType image/webp .webp\n";
160
-
161
return $rules;
162
}
163
@@ -208,40 +333,36 @@ class HTAccess
208
}
209
210
$propsToCompare = [
211
- 'forward-query-string',
212
- 'image-types',
213
- 'do-not-pass-source-in-query-string',
214
- 'redirect-to-existing-in-htaccess',
215
];
216
217
218
- foreach ($propsToCompare as $prop) {
219
if (!isset($newConfig[$prop])) {
220
continue;
221
}
222
if (!isset($oldConfig[$prop])) {
223
- if ($prop == 'do-not-pass-source-in-query-string') {
224
-
225
- // Do not trigger .htaccess update if the new value results
226
- // in same old behaviour (before this option was introduced)
227
- if ($newConfig[$prop] == false) {
228
- continue;
229
- } else {
230
- // Otherwise DO trigger .htaccess update
231
- return true;
232
- }
233
- }
234
- if ($prop == 'redirect-to-existing-in-htaccess') {
235
- if ($newConfig[$prop] == false) {
236
- continue;
237
- } else {
238
- return true;
239
- }
240
}
241
-
242
- // The option was not set in the old configuration,
243
- // - so lets say that .htaccess needs updating
244
- return true;
245
}
246
if ($newConfig[$prop] != $oldConfig[$prop]) {
247
return true;
16
17
class HTAccess
18
{
19
+ // (called from this file only. BUT our saveRules methods calls it, and it is called from several classes)
20
public static function generateHTAccessRulesFromConfigObj($config, $htaccessDir = 'index')
21
{
22
+ // Any option that is newer than ~v.0.2 may not be set yet.
23
+ // So, in order to not have to use isset() all over the place, set to values
24
+ // that results in same behaviour as before the option was introduced.
25
+ // Beware that this may not be same as the default value in the UI (but it is generally)
26
+ $defaults = [
27
+ 'enable-redirection-to-converter' => true,
28
+ 'forward-query-string' => true,
29
+ 'image-types' => 1,
30
+ 'do-not-pass-source-in-query-string' => false,
31
+ 'redirect-to-existing-in-htaccess' => false,
32
+ 'only-redirect-to-converter-on-cache-miss' => false,
33
+ 'destination-folder' => 'separate',
34
+ 'destination-extension' => 'append',
35
+ 'success-response' => 'converted',
36
+ ];
37
+ $config = array_merge($defaults, $config);
38
+
39
+ if ((!$config['enable-redirection-to-converter']) && (!$config['redirect-to-existing-in-htaccess'])) {
40
+ return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor to existing webp)';
41
+ }
42
+
43
/* Calculate $fileExt */
44
$imageTypes = $config['image-types'];
45
$fileExtensions = [];
52
$fileExt = implode('|', $fileExtensions);
53
54
if ($imageTypes == 0) {
55
+ return '# WebP Express disabled (no image types have been choosen to be converted/redirected)';
56
}
57
58
/* Build rules */
59
$rules = '';
60
61
+ /*
62
// The next line sets an environment variable.
63
// On the options page, we verify if this is set to diagnose if "AllowOverride None" is presented in 'httpd.conf'
64
//$rules .= "# The following SetEnv allows to diagnose if .htaccess files are turned off\n";
65
//$rules .= "SetEnv HTACCESS on\n\n";
66
+ */
67
68
$rules .= "<IfModule mod_rewrite.c>\n" .
69
" RewriteEngine On\n\n";
70
71
+ $cacheDirRel = Paths::getCacheDirRel() . '/doc-root';
72
+
73
+ $htaccessDirRel = '';
74
switch ($htaccessDir) {
75
case 'index':
76
+ $htaccessDirRel = Paths::getIndexDirRel();
77
break;
78
case 'home':
79
+ $htaccessDirRel = Paths::getHomeDirRel();
80
break;
81
case 'plugin':
82
+ $htaccessDirRel = Paths::getPluginDirRel();
83
break;
84
case 'uploads':
85
+ $htaccessDirRel = Paths::getUploadDirRel();
86
break;
87
case 'wp-content':
88
+ $htaccessDirRel = Paths::getWPContentDirRel();
89
break;
90
}
91
92
+ $passSourceInQS = !($config['do-not-pass-source-in-query-string']);
93
94
// TODO: Is it possible to handle when wp-content is outside document root?
95
99
// to get: RewriteRule ^\/?(.*)\.(jpe?g)$ /wp-content-moved/webp-express/webp-images/doc-root/plugins-moved/$1.$2.webp [NC,T=image/webp,QSD,E=WEBPACCEPT:1,E=EXISTING:1,L]
100
101
// https://stackoverflow.com/questions/34124819/mod-rewrite-set-custom-header-through-htaccess
102
+ $mingled = ($config['destination-folder'] == 'mingled');
103
+
104
+ if ($config['redirect-to-existing-in-htaccess']) {
105
+ if ($mingled) {
106
+ $rules .= " # Redirect to existing converted image in same dir (if browser supports webp)\n";
107
+ $rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
108
+
109
+ if ($config['destination-extension'] == 'append') {
110
+ $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.$2.webp -f\n";
111
+ $rules .= " RewriteRule ^(.+)\.(" . $fileExt . ")$ $1.$2.webp [T=image/webp,QSD,E=EXISTING:1,L]\n\n";
112
+ } else {
113
+ $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp -f\n";
114
+ $rules .= " RewriteRule ^(.+)\.(" . $fileExt . ")$ $1.webp [T=image/webp,QSD,E=EXISTING:1,L]\n\n";
115
+ }
116
+ }
117
+
118
$rules .= " # Redirect to existing converted image in cache-dir (if browser supports webp)\n";
119
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
120
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
121
+ $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp -f\n";
122
+ $rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,QSD,E=EXISTING:1,L]\n\n";
123
+
124
+ $cacheControlHeader = Config::getCacheControlHeader($config);
125
+ if ($cacheControlHeader != '') {
126
+
127
+ // Add Cache-Control header for webp files (this requires mod_headers)
128
+ // $rules .= "\n";
129
+ $rules .= " # Set Cache-Control header so these direct redirections also get cached\n";
130
+ $rules .= " <IfModule mod_headers.c>\n";
131
+ $rules .= " <FilesMatch \"\.webp$\">\n";
132
+ $rules .= " Header set Cache-Control \"" . $cacheControlHeader . "\"\n";
133
+ $rules .= " </FilesMatch>\n";
134
+ $rules .= " </IfModule>\n\n";
135
+
136
+ // Fall back to mod_expires if mod_headers is unavailable
137
+ $cacheControl = $config['cache-control'];
138
+ if ($cacheControl == 'custom') {
139
+ $expires = '';
140
+
141
+ // Do not add Expire header if private is set
142
+ // - because then the user don't want caching in proxies / CDNs.
143
+ // the Expires header doesn't differentiate between private/public
144
+ if (!(preg_match('/private/', $config['cache-control-custom']))) {
145
+ if (preg_match('/max-age=(\d+)/', $config['cache-control-custom'], $matches)) {
146
+ if (isset($matches[1])) {
147
+ $expires = $matches[1] . ' seconds';
148
+ }
149
+ }
150
+ }
151
+
152
+ } elseif ($cacheControl == 'no-header') {
153
+ $expires = '';
154
+ } elseif ($cacheControl == 'set') {
155
+ if ($config['cache-control-public']) {
156
+ $cacheControlOptions = [
157
+ 'no-header' => '',
158
+ 'one-second' => '1 seconds',
159
+ 'one-minute' => '1 minutes',
160
+ 'one-hour' => '1 hours',
161
+ 'one-day' => '1 days',
162
+ 'one-week' => '1 weeks',
163
+ 'one-month' => '1 months',
164
+ 'one-year' => '1 years',
165
+ ];
166
+ $expires = $cacheControlOptions[$config['cache-control-max-age']];
167
+ }
168
+ }
169
+
170
+ if ($expires != '') {
171
+ // in case mod_headers is missing, try mod_expires
172
+ $rules .= " # Fall back to mod_expires if mod_headers is unavailable\n";
173
+ $rules .= " <IfModule !mod_headers.c>\n";
174
+ $rules .= " <IfModule mod_expires.c>\n";
175
+ $rules .= " ExpiresActive On\n";
176
+ $rules .= " ExpiresByType image/webp \"access plus " . $expires . "\"\n";
177
+ $rules .= " </IfModule>\n";
178
+ $rules .= " </IfModule>\n\n";
179
+ }
180
+ }
181
+
182
+ }
183
+
184
+ if ($config['enable-redirection-to-converter']) {
185
+ $basicConditions = '';
186
+ if ($config['only-redirect-to-converter-for-webp-enabled-browsers']) {
187
+ $basicConditions = " RewriteCond %{HTTP_ACCEPT} image/webp\n";
188
+ }
189
+ $basicConditions .= " RewriteCond %{REQUEST_FILENAME} -f\n";
190
+ if ($config['only-redirect-to-converter-on-cache-miss']) {
191
+ if ($mingled) {
192
+ if ($config['destination-extension'] == 'append') {
193
+ $basicConditions .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.$2.webp !-f\n";
194
+ } else {
195
+ $basicConditions .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp !-f\n";
196
+ }
197
+ } else {
198
+ $basicConditions .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp !-f\n";
199
+ }
200
+ }
201
202
203
+ if (!$passSourceInQS) {
204
+ $rules .= " # Pass REQUEST_FILENAME to PHP by in request header\n";
205
+ $rules .= $basicConditions;
206
+ $rules .= " RewriteRule ^(.*)\.(" . $fileExt . ")$ - [E=REQFN:%{REQUEST_FILENAME}]\n" .
207
+ " <IfModule mod_headers.c>\n" .
208
+ " RequestHeader set REQFN \"%{REQFN}e\" env=REQFN\n" .
209
+ " </IfModule>\n\n";
210
+ }
211
212
+ $rules .= " # Redirect images to webp-on-demand.php ";
213
+ if ($config['only-redirect-to-converter-for-webp-enabled-browsers']) {
214
+ $rules .= "(if browser supports webp)\n";
215
+ } else {
216
+ $rules .= "(regardless whether browser supports webp or not!)\n";
217
+ }
218
+ if ($config['only-redirect-to-converter-on-cache-miss']) {
219
+ $rules .= " # - but only, when no existing converted image is found\n";
220
+ }
221
+ $rules .= $basicConditions;
222
223
+ if ($config['forward-query-string']) {
224
+ $rules .= " RewriteCond %{QUERY_STRING} (.*)\n";
225
+ }
226
+ /*
227
+ if ($config['forward-query-string']) {
228
+ }*/
229
+
230
+
231
+ // TODO:
232
+ // Add "NE" flag?
233
+ // https://github.com/rosell-dk/webp-convert/issues/95
234
+ // (and try testing spaces in directory paths)
235
+ $rules .= " RewriteRule ^(.*)\.(" . $fileExt . ")$ " .
236
+ "/" . Paths::getWodUrlPath() .
237
+ ($passSourceInQS ? "?xsource=x%{SCRIPT_FILENAME}&" : "?") .
238
+ "wp-content=" . Paths::getWPContentDirRel() .
239
+ ($config['forward-query-string'] ? '&%1' : '') .
240
+ " [NC,L]\n"; // E=WOD:1
241
+
242
+
243
+ $rules .= "\n <IfModule mod_headers.c>\n";
244
+ $rules .= " <IfModule mod_setenvif.c>\n";
245
+
246
+ if (($config['success-response'] == 'converted') || ($config['redirect-to-existing-in-htaccess'])) {
247
+ $rules .= " # Set Vary:Accept header for the image types handled by WebP Express.\n" .
248
+ " # The purpose is to make CDN aware that the response varies with the Accept header, so it should not just use the URL as cache key, but also the Accept header. \n" .
249
+ " SetEnvIf Request_URI \"\.(" . $fileExt . ")\" ADDVARY\n" .
250
+ " Header append \"Vary\" \"Accept\" env=ADDVARY\n\n";
251
+ }
252
253
+ $rules .= " # Set X-WebP-Express header for diagnose purposes\n" .
254
+ " # Apache appends \"REDIRECT_\" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not.\n" .
255
+ " # So, the next line is for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
256
+ " SetEnvIf REDIRECT_EXISTING 1 EXISTING=1\n" .
257
+ //" SetEnvIf REDIRECT_WOD 1 WOD=1\n\n" .
258
+ //" # Set the debug header\n" .
259
+ " Header set \"X-WebP-Express\" \"Redirected directly to existing webp\" env=EXISTING\n" .
260
+ //" Header set \"X-WebP-Express\" \"Redirected to image converter\" env=WOD\n" .
261
+ " </IfModule>\n" .
262
+ " </IfModule>\n\n";
263
+ }
264
+ $rules .="</IfModule>\n";
265
266
+ /*if ($config['redirect-to-existing-in-htaccess']) {
267
$rules .=
268
"<IfModule mod_headers.c>\n" .
269
" # Append Vary Accept header, when the rules above are redirecting to existing webp\n" .
280
"</IfModule>\n\n";
281
}*/
282
283
+ $rules .= "<IfModule mod_mime.c>\n";
284
+ $rules .= " AddType image/webp .webp\n";
285
+ $rules .= "</IfModule>\n";
286
return $rules;
287
}
288
333
}
334
335
$propsToCompare = [
336
+ 'forward-query-string' => true,
337
+ 'image-types' => 1,
338
+ 'do-not-pass-source-in-query-string' => false,
339
+ 'redirect-to-existing-in-htaccess' => false,
340
+ 'only-redirect-to-converter-on-cache-miss' => false,
341
+ 'success-response' => 'converted',
342
+ 'cache-control' => 'no-header',
343
+ 'cache-control-custom' => 'public, max-age:3600',
344
+ 'cache-control-max-age' => 'one-week',
345
+ 'cache-control-public' => true,
346
];
347
348
+ if (isset($newConfig['redirect-to-existing-in-htaccess']) && $newConfig['redirect-to-existing-in-htaccess']) {
349
+ $propsToCompare['destination-folder'] = 'separate';
350
+ $propsToCompare['destination-extension'] = 'append';
351
+ }
352
353
+ foreach ($propsToCompare as $prop => $behaviourBeforeIntroduced) {
354
if (!isset($newConfig[$prop])) {
355
continue;
356
}
357
if (!isset($oldConfig[$prop])) {
358
+ // Do not trigger .htaccess update if the new value results
359
+ // in same old behaviour (before this option was introduced)
360
+ if ($newConfig[$prop] == $behaviourBeforeIntroduced) {
361
+ continue;
362
+ } else {
363
+ // Otherwise DO trigger .htaccess update
364
+ return true;
365
}
366
}
367
if ($newConfig[$prop] != $oldConfig[$prop]) {
368
return true;
lib/classes/Paths.php CHANGED
@@ -13,8 +13,10 @@ class Paths
13
14
public static function createDirIfMissing($dir)
15
{
16
- if (!file_exists($dir)) {
17
- wp_mkdir_p($dir);
18
}
19
return file_exists($dir);
20
}
@@ -36,6 +38,34 @@ class Paths
36
return PathHelper::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir);
37
}
38
39
// ------------ Home Dir -------------
40
41
public static function getHomeDirAbs()
@@ -43,7 +73,7 @@ class Paths
43
if (!function_exists('get_home_path')) {
44
require_once ABSPATH . 'wp-admin/includes/file.php';
45
}
46
- return rtrim(get_home_path(), '/');
47
}
48
49
public static function getHomeDirRel()
@@ -56,7 +86,7 @@ class Paths
56
57
public static function getIndexDirAbs()
58
{
59
- return rtrim(ABSPATH, '/');
60
}
61
62
public static function getIndexDirRel()
@@ -88,7 +118,7 @@ class Paths
88
// ------------ WP Content Dir -------------
89
public static function getWPContentDirAbs()
90
{
91
- return rtrim(WP_CONTENT_DIR, '/');
92
}
93
public static function getWPContentDirRel()
94
{
@@ -111,10 +141,7 @@ class Paths
111
112
public static function getContentDirAbs()
113
{
114
- if (!defined(WP_CONTENT_DIR)) {
115
-
116
- }
117
- return rtrim(WP_CONTENT_DIR, '/') . '/webp-express';
118
}
119
120
public static function getContentDirRel()
@@ -131,7 +158,7 @@ class Paths
131
public static function getUploadDirAbs()
132
{
133
$upload_dir = wp_upload_dir(null, false);
134
- return $upload_dir['basedir'];
135
}
136
public static function getUploadDirRel()
137
{
13
14
public static function createDirIfMissing($dir)
15
{
16
+ if (!@file_exists($dir)) {
17
+ // We use the wp_mkdir_p, because it takes care of setting folder
18
+ // permissions to that of parent, and handles creating deep structures too
19
+ wp_mkdir_p($dir);
20
}
21
return file_exists($dir);
22
}
38
return PathHelper::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir);
39
}
40
41
+
42
+ /**
43
+ * Return absolute dir.
44
+ * - realpath() is used to resolve soft links and resolve '../' and './'
45
+ * - trailing dash is removed - we don't use that around here.
46
+ *
47
+ * realpath() only works on existing dirs.
48
+ * If realpath fails, PathHelper::canonicalize() will be used insead.
49
+ * (this takes care of resolving '../' and './', but does NOT resolve soft links)
50
+ */
51
+ public static function getAbsDir($dir)
52
+ {
53
+ $result = realpath($dir);
54
+ if ($result === false) {
55
+ $dir = PathHelper::canonicalize($dir);
56
+ } else {
57
+ $dir = $result;
58
+ }
59
+ return rtrim($dir, '/');
60
+ }
61
+
62
+ // ------------ Document Root -------------
63
+
64
+ public static function getDocumentRootAbs()
65
+ {
66
+ return self::getAbsDir($_SERVER["DOCUMENT_ROOT"]);
67
+ }
68
+
69
// ------------ Home Dir -------------
70
71
public static function getHomeDirAbs()
73
if (!function_exists('get_home_path')) {
74
require_once ABSPATH . 'wp-admin/includes/file.php';
75
}
76
+ return self::getAbsDir(get_home_path());
77
}
78
79
public static function getHomeDirRel()
86
87
public static function getIndexDirAbs()
88
{
89
+ return self::getAbsDir(ABSPATH);
90
}
91
92
public static function getIndexDirRel()
118
// ------------ WP Content Dir -------------
119
public static function getWPContentDirAbs()
120
{
121
+ return self::getAbsDir(WP_CONTENT_DIR);
122
}
123
public static function getWPContentDirRel()
124
{
141
142
public static function getContentDirAbs()
143
{
144
+ return self::getWPContentDirAbs() . '/webp-express';
145
}
146
147
public static function getContentDirRel()
158
public static function getUploadDirAbs()
159
{
160
$upload_dir = wp_upload_dir(null, false);
161
+ return self::getAbsDir($upload_dir['basedir']);
162
}
163
public static function getUploadDirRel()
164
{
lib/migrate/migrate.php CHANGED
@@ -29,6 +29,17 @@ if (!(State::getState('configured', false))) {
29
update_option('webp-express-migration-version', WEBPEXPRESS_MIGRATION_VERSION);
30
} else {
31
32
if (intval(get_option('webp-express-migration-version', 0)) == 0) {
33
// run migration 1
34
// It must take care of updating migration-version to 1, - if successful.
@@ -47,4 +58,5 @@ if (!(State::getState('configured', false))) {
47
// run migration 3
48
include __DIR__ . '/migrate3.php';
49
}
50
}
29
update_option('webp-express-migration-version', WEBPEXPRESS_MIGRATION_VERSION);
30
} else {
31
32
+ for ($x = intval(get_option('webp-express-migration-version', 0)); $x < WEBPEXPRESS_MIGRATION_VERSION; $x++) {
33
+ if (intval(get_option('webp-express-migration-version', 0)) == $x) {
34
+ // run migration X+1, which upgrades from X to X+1
35
+ // It must take care of updating the "webp-express-migration-version" option to X+1, - if successful.
36
+ // If unsuccessful, it must leaves the option unaltered, which will prevent
37
+ // newer migrations to run, until the problem with that migration is fixed.
38
+ include __DIR__ . '/migrate' . ($x + 1) . '.php';
39
+ }
40
+
41
+ }
42
+ /*
43
if (intval(get_option('webp-express-migration-version', 0)) == 0) {
44
// run migration 1
45
// It must take care of updating migration-version to 1, - if successful.
58
// run migration 3
59
include __DIR__ . '/migrate3.php';
60
}
61
+ */
62
}
lib/migrate/migrate4.php ADDED
@@ -0,0 +1,68 @@
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+
6
+ include_once __DIR__ . '/../classes/Config.php';
7
+ use \WebPExpress\Config;
8
+
9
+ include_once __DIR__ . '/../classes/Messenger.php';
10
+ use \WebPExpress\Messenger;
11
+
12
+
13
+ function webpexpress_migrate4() {
14
+ $config = Config::loadConfig();
15
+
16
+ if ($config !== false) {
17
+ if (isset($config['cache-control'])) {
18
+ switch ($config['cache-control']) {
19
+ case 'no-header':
20
+ break;
21
+ case 'custom':
22
+ break;
23
+ default:
24
+ $config['cache-control-max-age'] = $config['cache-control'];
25
+ $config['cache-control'] = 'set';
26
+ $config['cache-control-public'] = true;
27
+ Config::saveConfigurationFile($config);
28
+ }
29
+ }
30
+
31
+ if (isset($config['fail']) && ($config['fail'] != 'original')) {
32
+ $config['operation-mode'] = 'tweaked';
33
+ if (Config::saveConfigurationFile($config)) {
34
+ Messenger::addMessage(
35
+ 'info',
36
+ 'WebP Express 0.10 introduces <i>operation modes</i>. Your configuration <i>almost</i> fits the mode called ' .
37
+ '<i>Standard</i>, however as you have set the <i>Response on failure</i> option to something other than ' .
38
+ '<i>Original</i>, your setup has been put into <i>Tweaked</i> mode. ' .
39
+ '<a href="' . Paths::getSettingsUrl() . '">You might want to go and change that</a>.'
40
+ );
41
+ }
42
+ }
43
+
44
+ if (isset($config['redirect-to-existing-in-htaccess']) && ($config['redirect-to-existing-in-htaccess'])) {
45
+ Messenger::addMessage(
46
+ 'info',
47
+ 'In WebP Express 0.10, the <i>.htaccess</i> rules has been altered a bit: The Cache-Control header is now set when ' .
48
+ 'redirecting directly to an existing webp image.<br>' .
49
+ 'You might want to <a href="' . Paths::getSettingsUrl() . '">go to the options page</a> and re-save settings in order to regenerate the <i>.htaccess</i> rules.'
50
+ );
51
+ }
52
+
53
+ if (!isset($config['redirect-to-existing-in-htaccess'])) {
54
+ Messenger::addMessage(
55
+ 'info',
56
+ 'In WebP Express 0.10, the "Redirect directly to converted image when available" option is no longer in beta. ' .
57
+ 'You might want to <a href="' . Paths::getSettingsUrl() . '">go and activate it</a>.'
58
+ );
59
+ }
60
+
61
+ }
62
+
63
+ // PSST: When creating new migration files, remember to update WEBPEXPRESS_MIGRATION_VERSION in admin.php
64
+ update_option('webp-express-migration-version', '4');
65
+
66
+ }
67
+
68
+ webpexpress_migrate4();
lib/options/css/webp-express-options-page.css CHANGED
@@ -159,7 +159,36 @@
159
min-width: 150px;
160
font-weight: normal;
161
text-align: left;
162
- max-width: 450px;
163
}
164
#converters li.operational .popup {
165
background-color: #80ff80;
@@ -174,9 +203,6 @@
174
175
}
176
177
- .help .popup {
178
- min-width: 250px;
179
- }
180
181
/*
182
#converters li > a.remove-converter {
@@ -227,6 +253,13 @@
227
font-family: sans-serif;
228
position: relative;
229
}
230
231
/*
232
.converter-options.wpc #wpc_web_services_div > p {
159
min-width: 150px;
160
font-weight: normal;
161
text-align: left;
162
+ }
163
+
164
+ .help .popup.narrow {
165
+ width: 200px;
166
+ }
167
+ .help .popup {
168
+ width: 250px;
169
+ }
170
+ .help .popup.wide {
171
+ width: 350px;
172
+ }
173
+ .help .popup.wider {
174
+ width: 450px;
175
+ }
176
+ @media (max-width: 500px) {
177
+ .help .popup {
178
+ max-width: 380px;
179
+ }
180
+ }
181
+ @media (max-width: 400px) {
182
+ .help .popup {
183
+ max-width: 280px;
184
+ }
185
+ }
186
+
187
+ .help .popup > p:first-child {
188
+ margin-top: 0;
189
+ }
190
+ .help .popup > p:last-child {
191
+ margin-bottom: 0;
192
}
193
#converters li.operational .popup {
194
background-color: #80ff80;
203
204
}
205
206
207
/*
208
#converters li > a.remove-converter {
253
font-family: sans-serif;
254
position: relative;
255
}
256
+ .help.no-margin-left {
257
+ margin-left: 0px;
258
+ }
259
+ .help.set-margin-right {
260
+ margin-right: 7px;
261
+ }
262
+
263
264
/*
265
.converter-options.wpc #wpc_web_services_div > p {
lib/options/enqueue_scripts.php CHANGED
@@ -6,7 +6,7 @@ use \WebPExpress\Paths;
6
include_once __DIR__ . '/../classes/Config.php';
7
use \WebPExpress\Config;
8
9
- $version = '0.9.1';
10
11
function webp_express_add_inline_script($id, $script, $position) {
12
if (function_exists('wp_add_inline_script')) {
@@ -24,17 +24,30 @@ wp_enqueue_script('daspopup');
24
25
$config = Config::getConfigForOptionsPage();
26
27
- // Converters
28
- wp_register_script('converters', plugins_url('js/converters.js', __FILE__), ['sortable','daspopup'], $version);
29
- webp_express_add_inline_script('converters', 'window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';', 'before');
30
- webp_express_add_inline_script('converters', 'window.converters = ' . json_encode($config['converters']) . ';', 'before');
31
- wp_enqueue_script('converters');
32
33
34
- // Whitelist
35
- wp_register_script('whitelist', plugins_url('js/whitelist.js', __FILE__), ['daspopup'], $version);
36
- webp_express_add_inline_script('whitelist', 'window.whitelist = ' . json_encode($config['web-service']['whitelist']) . ';', 'before');
37
- wp_enqueue_script('whitelist');
38
39
//wp_register_script('api_keys', plugins_url('js/api-keys.js', __FILE__), ['daspopup'], '0.7.0-dev8');
40
//wp_enqueue_script('api_keys');
6
include_once __DIR__ . '/../classes/Config.php';
7
use \WebPExpress\Config;
8
9
+ $version = '0.10.0';
10
11
function webp_express_add_inline_script($id, $script, $position) {
12
if (function_exists('wp_add_inline_script')) {
24
25
$config = Config::getConfigForOptionsPage();
26
27
+ if (!(isset($config['operation-mode']) && $config['operation-mode'] == 'just-redirect')) {
28
29
+ // Remove empty options arrays.
30
+ // These cause trouble in json because they are encoded as [] rather than {}
31
32
+ foreach ($config['converters'] as &$converter) {
33
+ if (isset($converter['options']) && (count(array_keys($converter['options'])) == 0)) {
34
+ unset($converter['options']);
35
+ }
36
+ }
37
+
38
+ // Converters
39
+ wp_register_script('converters', plugins_url('js/converters.js', __FILE__), ['sortable','daspopup'], $version);
40
+ webp_express_add_inline_script('converters', 'window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';', 'before');
41
+ webp_express_add_inline_script('converters', 'window.converters = ' . json_encode($config['converters']) . ';', 'before');
42
+ wp_enqueue_script('converters');
43
+
44
+
45
+ // Whitelist
46
+ wp_register_script('whitelist', plugins_url('js/whitelist.js', __FILE__), ['daspopup'], $version);
47
+ webp_express_add_inline_script('whitelist', 'window.whitelist = ' . json_encode($config['web-service']['whitelist']) . ';', 'before');
48
+ wp_enqueue_script('whitelist');
49
+
50
+ }
51
52
//wp_register_script('api_keys', plugins_url('js/api-keys.js', __FILE__), ['daspopup'], '0.7.0-dev8');
53
//wp_enqueue_script('api_keys');
lib/options/js/converters.js CHANGED
@@ -5,50 +5,6 @@ window.convertersMap = {};
5
6
window.currentlyEditing = '';
7
8
- function resetToDefaultConverters() {
9
- window.converters = window.defaultConverters;
10
- }
11
-
12
- function addMissingConvertersAndOptions() {
13
- // check if all available converters are in the array.
14
- // if not - add!
15
- // the double loop could be avoided with map. But arrays are so small, so not worth it
16
- /*
17
- for (var i=0; i<window.defaultConverters.length; i++) {
18
- var checkMe = window.defaultConverters[i];
19
- var found = false;
20
- for (var j=0; j<window.converters.length; j++) {
21
- var checkMe2 = window.converters[j]
22
- if (checkMe2['converter'] == checkMe['converter']) {
23
- found = true;
24
- // at a earilier point in time, we accidently added options as an
25
- // empty array instead of an empty object. Correcting that!
26
- if (checkMe2['options'] && checkMe2['options'].constructor === Array) {
27
- checkMe2['options'] = {};
28
- }
29
- if (checkMe['options']) {
30
- for (var optionName in checkMe['options']) {
31
- if (checkMe['options'].hasOwnProperty(optionName)) {
32
- if (!checkMe2['options']) {
33
- checkMe2['options'] = {};
34
- }
35
- if (!checkMe2['options'].hasOwnProperty(optionName)) {
36
- checkMe2['options'][optionName] = checkMe['options'][optionName];
37
- }
38
- }
39
- }
40
- }
41
- }
42
- }
43
- if (!found) {
44
- window.converters.push(window.defaultConverters[i]);
45
- }
46
-
47
- }*/
48
- //console.log(window.converters);
49
- //console.log(window.defaultConverters);
50
- }
51
-
52
function getConversionMethodDescription(converterId) {
53
var descriptions = {
54
'cwebp': '<i>cwebp</i> binary',
@@ -211,8 +167,6 @@ function setConvertersHTML() {
211
}
212
213
document.addEventListener('DOMContentLoaded', function() {
214
- //resetToDefaultConverters();
215
- addMissingConvertersAndOptions();
216
setConvertersHTML();
217
});
218
@@ -252,7 +206,6 @@ function deleteConverterOption(converter, optionName) {
252
function configureConverter(id) {
253
var converter = window.convertersMap[id];
254
window.currentlyEditing = id;
255
-
256
var q = getConverterOption(converter, 'quality', 'auto');