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$/", '.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');
257
  if (document.getElementById(id + '_quality')) {
258
  document.getElementById(id + '_quality').value = q;
@@ -417,6 +370,7 @@ function updateConverterOptions() {
417
  setConverterOption(converter, 'use-nice', document.getElementById('imagickbinary_use_nice').checked);
418
  break;
419
  }
 
420
  updateInputValue();
421
  tb_remove();
422
  }
@@ -573,109 +527,3 @@ function wpcApiVersionChanged() {
573
  document.getElementById('wpc_api_key_div').style.display = 'block';
574
  }
575
  }
576
- /*
577
- function wpcClearPropertiesForm() {
578
- document.getElementById('wpc_label').value = '';
579
- document.getElementById('wpc_url').value = '';
580
- document.getElementById('wpc_api_key').value = '';
581
- document.getElementById('wpc_crypt_api_key_in_transfer').checked = true;
582
- }
583
-
584
- function wpcRemoveEntry(i) {
585
- var id = window.currentlyEditing;
586
- var converter = window.convertersMap[id];
587
-
588
- converter['options']['web-services'].splice(i, 1);
589
- wpcUpdateWebServicesHTML();
590
- }
591
- function wpcUpdateWebServicesHTML() {
592
- var id = window.currentlyEditing;
593
- var converter = window.convertersMap[id];
594
-
595
- //setConverterOption(converter, 'web-services', )
596
- var s = '';
597
-
598
- var webServices = getConverterOption(converter, 'web-services', []);
599
-
600
- if (webServices.length == 0) {
601
- s += '<p>Not connected to any web service yet</p>';
602
- } else {
603
- s += '<ul>';
604
- for (var i=0; i<webServices.length; i++) {
605
- s+='<li>';
606
- s+=webServices[i].label;
607
- s+='<div class="wpc-links">'
608
- s+='<a href="javascript:wpcEditEntry(' + i + ')">edit</a>';
609
- s+='<a href="javascript:wpcRemoveEntry(' + i + ')">remove</a>';
610
- s+='</div>'
611
- s+='</li>';
612
- }
613
- s += '</ul>';
614
- }
615
-
616
- document.getElementById('wpc_web_services_div').innerHTML = s;
617
- }
618
-
619
- function wpcAddEntry() {
620
- var id = window.currentlyEditing;
621
- var converter = window.convertersMap[id];
622
-
623
- if (!converter['options']['web-services']) {
624
- converter['options']['web-services'] = [];
625
- }
626
- converter['options']['web-services'].push(
627
- {
628
- 'label' : document.getElementById('wpc_label').value,
629
- 'url' : document.getElementById('wpc_url').value,
630
- 'new-api-key': document.getElementById('wpc_api_key').value,
631
- 'crypt-api-key-in-transfer': document.getElementById('wpc_crypt_api_key_in_transfer').checked
632
- }
633
- )
634
- wpcUpdateWebServicesHTML();
635
- closeDasPopup();
636
- }
637
-
638
- function wpcUpdateEntry() {
639
- var id = window.currentlyEditing;
640
- var converter = window.convertersMap[id];
641
- var i = parseInt(document.getElementById('wpc_i').value, 10);
642
- var settings = converter['options']['web-services'][i];
643
-
644
- // TODO: validate
645
- settings['label'] = document.getElementById('wpc_label').value;
646
- settings['url'] = document.getElementById('wpc_url').value;
647
- settings['crypt-api-key-in-transfer'] = document.getElementById('wpc_crypt_api_key_in_transfer').checked;
648
-
649
- wpcUpdateWebServicesHTML();
650
- closeDasPopup();
651
-
652
- }
653
-
654
- function wpcEditEntry(i) {
655
- var id = window.currentlyEditing;
656
- var converter = window.convertersMap[id];
657
- var settings = converter['options']['web-services'][i];
658
-
659
- // todo: uid?
660
- // TODO: api keys must be removed in page.php
661
- // TODO: api-version
662
- document.getElementById('wpc_i').value = i;
663
- document.getElementById('wpc_label').value = settings['label'];
664
- document.getElementById('wpc_url').value = settings['url'];
665
- document.getElementById('wpc_new_api_key').value = '';
666
- document.getElementById('wpc_crypt_api_key_in_transfer').checked = settings['crypt-api-key-in-transfer'];
667
-
668
- document.getElementById('wpc_properties_popup').className = 'das-popup mode-edit';
669
- openDasPopup('wpc_properties_popup', 500, 350);
670
- }
671
-
672
-
673
- function wpcAddManually() {
674
- closeDasPopup();
675
-
676
- wpcClearPropertiesForm();
677
-
678
- document.getElementById('wpc_properties_popup').className = 'das-popup mode-add';
679
- openDasPopup('wpc_properties_popup', 500, 350);
680
-
681
- }*/
5
 
6
  window.currentlyEditing = '';
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  function getConversionMethodDescription(converterId) {
9
  var descriptions = {
10
  'cwebp': '<i>cwebp</i> binary',
167
  }
168
 
169
  document.addEventListener('DOMContentLoaded', function() {
 
 
170
  setConvertersHTML();
171
  });
172
 
206
  function configureConverter(id) {
207
  var converter = window.convertersMap[id];
208
  window.currentlyEditing = id;
 
209
  var q = getConverterOption(converter, 'quality', 'auto');
210
  if (document.getElementById(id + '_quality')) {
211
  document.getElementById(id + '_quality').value = q;
370
  setConverterOption(converter, 'use-nice', document.getElementById('imagickbinary_use_nice').checked);
371
  break;
372
  }
373
+
374
  updateInputValue();
375
  tb_remove();
376
  }
527
  document.getElementById('wpc_api_key_div').style.display = 'block';
528
  }
529
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/options/js/page.js CHANGED
@@ -1,23 +1,44 @@
1
-
 
 
 
 
 
 
 
 
 
 
 
 
2
  function updateCacheControlCustomVisibility() {
3
 
4
  if (document.getElementById('cache_control_select') == null) {
5
- // this ought not to happen,
6
- // but it does? https://wordpress.org/support/topic/console-errors-9/#post-11018243
7
- alert('document.getElementById("cache_control_select") returns null. Strange! Please report.');
8
  return;
9
  }
10
  if (document.getElementById('cache_control_custom') == null) {
11
  alert('document.getElementById("cache_control_custom") returns null. Strange! Please report.');
12
  return;
13
  }
 
 
 
 
 
14
  var cacheControlValue = document.getElementById('cache_control_select').value;
 
15
  var customEl = document.getElementById('cache_control_custom');
16
  if (cacheControlValue == 'custom') {
17
  customEl.setAttribute('type', 'text');
18
  } else {
19
  customEl.setAttribute('type', 'hidden');
20
- }
 
 
 
 
 
21
  }
22
 
23
  function updateQualityVisibility() {
@@ -37,6 +58,22 @@ function updateQualityVisibility() {
37
  maxQualityRowEl.style['display'] = 'none';
38
  qualitySpecificRowEl.style['display'] = 'table-row';
39
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  }
42
 
@@ -56,10 +93,13 @@ document.addEventListener('DOMContentLoaded', function() {
56
  updateCacheControlCustomVisibility();
57
  updateQualityVisibility();
58
  updateServerSettingsVisibility();
 
59
 
60
- document.getElementById('cache_control_select').addEventListener('change', function() {
61
- updateCacheControlCustomVisibility();
62
- });
 
 
63
 
64
  if (document.getElementById('quality_auto_select')) {
65
  document.getElementById('quality_auto_select').addEventListener('change', function() {
@@ -67,22 +107,38 @@ document.addEventListener('DOMContentLoaded', function() {
67
  });
68
  }
69
 
70
- document.getElementById('web_service_enabled').addEventListener('change', function() {
71
- updateServerSettingsVisibility();
72
- });
 
 
 
 
 
 
 
 
73
 
74
- // Dot animation
75
- window.setInterval(function() {
76
- var dotElms = document.getElementsByClassName('animated-dots');
77
- for (var i=0; i<dotElms.length; i++) {
78
- var el = dotElms[i];
79
- if (el.innerText == '....') {
80
- el.innerText = '';
81
  } else {
82
- el.innerText += '.';
83
  }
84
  }
85
- }, 500);
86
 
87
- //alert(sortable.toArray());
 
 
 
 
 
 
 
 
 
88
  });
1
+ function setOptionVisibility(elmId, show) {
2
+ var elm = document.getElementById(elmId);
3
+ if (!elm) {
4
+ return;
5
+ }
6
+ if (show) {
7
+ elm.style['visibility'] = 'visible';
8
+ elm.style['position'] = 'static';
9
+ } else {
10
+ elm.style['visibility'] = 'hidden';
11
+ elm.style['position'] = 'absolute';
12
+ }
13
+ }
14
  function updateCacheControlCustomVisibility() {
15
 
16
  if (document.getElementById('cache_control_select') == null) {
17
+ // Well, it seems that the cache control option isn't available in this operation mode
 
 
18
  return;
19
  }
20
  if (document.getElementById('cache_control_custom') == null) {
21
  alert('document.getElementById("cache_control_custom") returns null. Strange! Please report.');
22
  return;
23
  }
24
+ if (document.getElementById('cache_control_public') == null) {
25
+ alert('document.getElementById("cache_control_public") returns null. Strange! Please report.');
26
+ return;
27
+ }
28
+
29
  var cacheControlValue = document.getElementById('cache_control_select').value;
30
+ /*
31
  var customEl = document.getElementById('cache_control_custom');
32
  if (cacheControlValue == 'custom') {
33
  customEl.setAttribute('type', 'text');
34
  } else {
35
  customEl.setAttribute('type', 'hidden');
36
+ }*/
37
+
38
+ setOptionVisibility('cache_control_custom_div', (cacheControlValue == 'custom'));
39
+
40
+ setOptionVisibility('cache_control_set_div', (cacheControlValue == 'set'));
41
+
42
  }
43
 
44
  function updateQualityVisibility() {
58
  maxQualityRowEl.style['display'] = 'none';
59
  qualitySpecificRowEl.style['display'] = 'table-row';
60
  }
61
+ }
62
+
63
+ function updateDestinationExtensionVisibility() {
64
+ var destinationFolderEl = document.getElementById('destination_folder');
65
+ if (!destinationFolderEl) {
66
+ return;
67
+ }
68
+ var destinationFolderValue = destinationFolderEl.value;
69
+ var destinationExtensionEl = document.getElementById('destination_extension_row');
70
+
71
+ //alert(qualityAutoValue);
72
+ if (destinationFolderValue == 'mingled') {
73
+ destinationExtensionEl.style['display'] = 'table-row';
74
+ } else {
75
+ destinationExtensionEl.style['display'] = 'none';
76
+ }
77
 
78
  }
79
 
93
  updateCacheControlCustomVisibility();
94
  updateQualityVisibility();
95
  updateServerSettingsVisibility();
96
+ updateDestinationExtensionVisibility();
97
 
98
+ if (document.getElementById('cache_control_select')) {
99
+ document.getElementById('cache_control_select').addEventListener('change', function() {
100
+ updateCacheControlCustomVisibility();
101
+ });
102
+ }
103
 
104
  if (document.getElementById('quality_auto_select')) {
105
  document.getElementById('quality_auto_select').addEventListener('change', function() {
107
  });
108
  }
109
 
110
+ if (document.getElementById('destination_folder')) {
111
+ document.getElementById('destination_folder').addEventListener('change', function() {
112
+ updateDestinationExtensionVisibility();
113
+ });
114
+ }
115
+
116
+ if (document.getElementById('web_service_enabled')) {
117
+ document.getElementById('web_service_enabled').addEventListener('change', function() {
118
+ updateServerSettingsVisibility();
119
+ });
120
+ }
121
 
122
+ document.getElementById('change_operation_mode').addEventListener('change', function() {
123
+ var msg;
124
+ if (document.getElementById('operation_mode').value == 'tweaked') {
125
+ msg = 'Save configuration and change mode? Any tweaks will be lost';
126
+ } else {
127
+ if (document.getElementById('change_operation_mode').value == 'tweaked') {
128
+ msg = 'Save configuration and change to tweaked mode? No options are lost when changing to tweaked mode (it will behave the same way as currently, until you start tweaking)';
129
  } else {
130
+ msg = 'Save configuration and change mode?';
131
  }
132
  }
 
133
 
134
+ if (confirm(msg)) {
135
+ document.getElementById('webpexpress_settings').submit();
136
+ } else {
137
+ // undo select box change
138
+ document.getElementById('change_operation_mode').value = document.getElementById('operation_mode').value;
139
+ return;
140
+ }
141
+
142
+ });
143
+
144
  });
lib/options/options/cache-control.inc DELETED
@@ -1,25 +0,0 @@
1
- <?php
2
-
3
- //$maxQuality = get_option('webp_express_max_quality');
4
- $cacheControl = $config['cache-control'];
5
- $cacheControlCustom = $config['cache-control-custom'];
6
-
7
- echo '<tr><th scope="row">Caching';
8
- echo helpIcon(
9
- 'Controls the cache-control header for the converted image. ' .
10
- 'This header is only sent when a converted image can be successfully delivered (either existing, or new ' .
11
- 'conversion). In case of failure, headers will be sent to prevent caching.');
12
- echo '</th><td>';
13
- echo '<select id="cache_control_select" name="cache-control">';
14
- echo '<option value="no-header"' . ($cacheControl == 'no-header' ? ' selected' : '') . '>Do not set Cache-Control header</option>';
15
- echo '<option value="one-second"' . ($cacheControl == 'one-second' ? ' selected' : '') . '>One second</option>';
16
- echo '<option value="one-minute"' . ($cacheControl == 'one-minute' ? ' selected' : '') . '>One minute</option>';
17
- echo '<option value="one-hour"' . ($cacheControl == 'one-hour' ? ' selected' : '') . '>One hour</option>';
18
- echo '<option value="one-day"' . ($cacheControl == 'one-day' ? ' selected' : '') . '>One day</option>';
19
- echo '<option value="one-week"' . ($cacheControl == 'one-week' ? ' selected' : '') . '>One week</option>';
20
- echo '<option value="one-month"' . ($cacheControl == 'one-month' ? ' selected' : '') . '>One month</option>';
21
- echo '<option value="one-year"' . ($cacheControl == 'one-year' ? ' selected' : '') . '>One year</option>';
22
- echo '<option value="custom"' . ($cacheControl == 'custom' ? ' selected' : '') . '>Custom Cache-Control header</option>';
23
- echo '</select><br>';
24
- echo '<input type="text" id="cache_control_custom" name="cache-control-custom" value="' . $cacheControlCustom . '">';
25
- echo '</td></tr>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/options/options/conversion-options/conversion-options.inc ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($config['operation-mode'] == 'tweaked') : ?>
2
+ <fieldset class="block">
3
+ <h3>Conversion options</h3>
4
+ <p><i>The options here affects the conversion process</i></p>
5
+ <table class="form-table">
6
+ <tbody>
7
+ <?php
8
+ include_once 'quality.inc';
9
+ include_once 'metadata.inc';
10
+ include_once 'converters.inc';
11
+ include_once 'destination-folder.inc';
12
+ include_once 'destination-extension.inc';
13
+ ?>
14
+ </tbody>
15
+ </table>
16
+ </fieldset>
17
+ <?php
18
+ else:
19
+ include_once 'quality.inc';
20
+ include_once 'metadata.inc';
21
+ include_once 'converters.inc';
22
+
23
+ if ($config['operation-mode'] == 'standard') {
24
+ include_once 'destination-folder.inc';
25
+ }
26
+ include_once 'destination-extension.inc';
27
+ endif;
28
+ ?>
lib/options/options/{converter-options → conversion-options/converter-options}/cwebp.php RENAMED
File without changes
lib/options/options/{converter-options → conversion-options/converter-options}/ewww.php RENAMED
File without changes
lib/options/options/{converter-options → conversion-options/converter-options}/gd.php RENAMED
File without changes
lib/options/options/{converter-options → conversion-options/converter-options}/imagick.php RENAMED
File without changes
lib/options/options/{converter-options → conversion-options/converter-options}/imagickbinary.php RENAMED
File without changes
lib/options/options/{converter-options → conversion-options/converter-options}/wpc.php RENAMED
File without changes
lib/options/options/{converters.inc → conversion-options/converters.inc} RENAMED
File without changes
lib/options/options/conversion-options/destination-extension.inc ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Image types
3
+ // ------------
4
+ echo '<tr id="destination_extension_row"><th scope="row">File extension';
5
+ echo helpIcon('<p>Controls the filename of the converted file.</p><p>The "Append" option result in file names such as "image.png.webp". The "Set" option results in file names such as "image.webp". Note that if you choose "Set", it will be a problem if you ie both have a logo.jpg and a logo.png in the same folder. If you are using WebP Express together with <a target="blank" href="https://da.wordpress.org/plugins/cache-enabler/">Cache enabler</a> or <a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>, set this option to Set"</p><p>Changing this option will cause existing webp images to be renamed (only those in the upload folder, and only those that has a corresponding source image)</p>');
6
+ echo '</th><td>';
7
+
8
+ $destinationExtension = $config['destination-extension'];
9
+
10
+ echo '<select name="destination-extension">';
11
+ echo '<option value="append"' . ($destinationExtension == 'append' ? ' selected' : '') . '>Append ".webp"</option>';
12
+ echo '<option value="set"' . ($destinationExtension == 'set' ? ' selected' : '') . '>Set to ".webp"</option>';
13
+ echo '</select>';
14
+
15
+ echo '</td></tr>';
lib/options/options/conversion-options/destination-folder.inc ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Image types
3
+ // ------------
4
+ echo '<tr><th scope="row">Destination folder';
5
+ echo helpIcon('<p>If "In separate folder" is selected, the webp images will be saved to a common root (wp-content/webp-express/webp-images/doc-root), mirroring the folder structure. If on the other hand, "Mingled" is selected, the converter will try to save the webp image in the same folder as the original. If that fails (ie for theme or plugin images), the image will be saved in the separate folder. If you are using WebP Express together with <a target="blank" href="https://da.wordpress.org/plugins/cache-enabler/">Cache enabler</a> or <a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>, set this option to "mingled"</p><p>Changing this option will cause existing webp images to be moved (only those in the upload folder, and only those that has a corresponding source image)</p>');
6
+ echo '</th><td>';
7
+
8
+ $destinationFolder = $config['destination-folder'];
9
+
10
+ echo '<select name="destination-folder" id="destination_folder">';
11
+ echo '<option value="separate"' . ($destinationFolder == 'separate' ? ' selected' : '') . '>In separate folder</option>';
12
+ echo '<option value="mingled"' . ($destinationFolder == 'mingled' ? ' selected' : '') . '>Mingled</option>';
13
+ echo '</select>';
14
+
15
+ echo '</td></tr>';
lib/options/options/{metadata.inc → conversion-options/metadata.inc} RENAMED
File without changes
lib/options/options/{quality.inc → conversion-options/quality.inc} RENAMED
File without changes
lib/options/options/do-not-pass-source-path-in-query-string.inc DELETED
@@ -1,6 +0,0 @@
1
- <?php
2
- echo '<tr><th scope="row"><span style="color:darkorange">Beta:</span> Do not pass source in Query String';
3
- echo helpIcon('In v0.8 and below, the .htaccess always passed the filename of the image to the script through the query string. It however seems that the PHP can determine the original file name in a server variable, and therefore does not need it in the query string. Removing it has the benefit of reducing risk of firewalls blocking the request. This option will probably go away in the next release, when it is confirmed that the feature is stable. So you better test if it works now, thanks!');
4
- echo '</th><td>';
5
- echo '<input type="checkbox" id="do_not_pass_source_in_query_string" name="do-not-pass-source-in-query-string" value="true" ' . ($config['do-not-pass-source-in-query-string'] ? 'checked="checked"' : '') . '">';
6
- echo '</td></tr>';
 
 
 
 
 
 
lib/options/options/image-types.inc DELETED
@@ -1,22 +0,0 @@
1
- <?php
2
- // Image types
3
- // ------------
4
- echo '<tr><th scope="row">Image types to send to the converter';
5
- echo helpIcon('Beware that the Gd conversion method cannot handle transparency for PNGs. PNG conversions havent been tested much yet. Please report any problems with PNG images <a target="_blank" href="https://github.com/rosell-dk/webp-convert/issues/42">here</a>');
6
- echo '</th><td>';
7
-
8
- // bitmask
9
- // 1: JPEGs
10
- // 2: PNG's
11
- // Converting only jpegs is thus "1"
12
- // Converting both jpegs and pngs is (1+2) = 3
13
- //$imageTypes = get_option('webp_express_image_types_to_convert');
14
- $imageTypes = $config['image-types'];
15
-
16
- echo '<select name="image-types">';
17
- echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>Do not convert any images!</option>';
18
- echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only convert jpegs</option>';
19
- echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Convert both jpegs and pngs</option>';
20
- echo '</select>';
21
-
22
- echo '</td></tr>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/options/options/operation-mode.inc ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ $operationMode = $config['operation-mode'];
3
+ ?>
4
+ <fieldset class="block">
5
+ <h3>Operation mode: <?php echo helpIcon('<p>Think of the operation modes as presets that matches normal use-cases. Usually you want to stick with <i>Standard</i> or perhaps <i>Just convert</i>. The Tweaked mode has no presets. That is: Here, you can set all options.</p><p>Changing from ie. "Standard" mode to "Tweaked" mode enables you to see the underlying options for that mode (and tweak them). Changing back will override the tweaks (you will lose them).</p><p>You will never loose your converter configurations by changing mode</p>');?>
6
+ <input type="hidden" name="operation-mode" id="operation_mode" value="<?php echo $operationMode ?>">
7
+ <select name="change-operation-mode" id="change_operation_mode">
8
+ <option value="standard"<?php if ($operationMode == 'standard') echo ' selected'?>>Standard</option>
9
+ <option value="just-convert"<?php if ($operationMode == 'just-convert') echo ' selected'?>>Just convert</option>
10
+ <option value="just-redirect"<?php if ($operationMode == 'just-redirect') echo ' selected'?>>Just redirect</option>
11
+ <option value="tweaked"<?php if ($operationMode == 'tweaked') echo ' selected'?>>Tweaked</option>
12
+ </select></h3>
13
+
14
+ <?php if ($config['operation-mode'] == 'standard') : ?>
15
+ <p><div>
16
+ <i>In standard mode, WebP Express takes care of serving autogenerated WebP images instead of jpeg/png to browsers that supports WebP.</i>
17
+ <div class="help">?<div class="popup">
18
+ <i>WebP Express serves webp to browsers that supports it by redirecting
19
+ the original image request (internally on server, it is not a 301 redirect).
20
+ If no webp is yet generated, the request will be redirected to the image converter
21
+ which generates the webp. On subsequent requests, the image is redirected (again, internally) directly
22
+ to the webp, by means of a rule in the .htaccess.</i>
23
+ </div>
24
+ </div>
25
+ </div></p>
26
+ <?php endif; ?>
27
+ <?php if ($config['operation-mode'] == 'just-convert') : ?>
28
+ <p><div>
29
+ <i>In "just convert" mode, WebP Express is used just for converting.<br>
30
+ Combined with a plugin that can alter HTML for webp-use (ie <a target="_blank" href="https://da.wordpress.org/plugins/cache-enabler/">Cache Enabler</a> or <a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>), the redirection trick can be avoided.</i>
31
+ <div class="help">?<div class="popup">
32
+ <p>
33
+ Benefits of avoiding the redirection trick:
34
+ <ol>
35
+ <li>CDN's will not have to be set up to forward the Accept header</li>
36
+ <li>If a user downloads an image, it will not have wrong file extension</li>
37
+ </ol>
38
+ PS: Instructions for using WebPExpress with Cache Enabler and Shortpixel are found in the FAQ
39
+ </p>
40
+ <p>
41
+ PPS: Images refererenced from CSS or added dynamically with javascript will not be served as webp in this mode.
42
+ </p>
43
+ </div>
44
+ </div>
45
+ <br><br>
46
+ <i>An auto-convert option is available!</i>
47
+ <div class="help">?<div class="popup">
48
+ <em>The auto-convertion works this way: When an image is requested, a rule in the .htaccess detects if
49
+ that image has been converted. If not, the request is redirected to the converter, which creates the
50
+ webp and returns <em>the original</em> image</em>
51
+ </div>
52
+ </div>
53
+ </div></p>
54
+ <?php endif; ?>
55
+ <?php if ($config['operation-mode'] == 'just-redirect') : ?>
56
+ <p>
57
+ <div>
58
+ <i>In "just redirect" mode, WebP Express is used just for redirecting jpeg and pngs to existing webp images in the same folder.<br>
59
+ This mode can for example be used in combination with Optimus HQ.</i>
60
+ </div>
61
+ </p>
62
+ <?php endif; ?>
63
+ </fieldset>
64
+ <!--
65
+ <p><div>
66
+ <i>WebP Express takes care of serving autogenerated WebP images instead of jpeg/png to browsers that supports WebP.</i>
67
+ <div class="help">?<div class="popup"><ol>
68
+ <li>Some redirect rules set up in <i>.htaccess</i> redirects (unconverted) jpeg/png images to a PHP script for handling.</li>
69
+ <li>The PHP script reads the options and passes them to the <i><a target="_blank" href="https://github.com/rosell-dk/webp-convert/">WebP Convert</a></i> library for converting <i>and</i> serving.</li>
70
+ <li>If WebP Convert finds that the image already is converted, it will be served immediately (unless it is bigger than the original, or the original has been modified). Otherwise it will be converted and then served</li>
71
+ </ol></div></div>
72
+ </div></p>
73
+ -->
lib/options/options/redirect-to-existing.inc DELETED
@@ -1,8 +0,0 @@
1
- <?php
2
- // Redirect to existing with .htaccess
3
- // --------------------------------
4
- echo '<tr><th scope="row"><span style="color:darkorange">Beta:</span> Redirect directly to converted image when available';
5
- echo helpIcon('This will add rules in the .htaccess that redirects directly to existing converted files. If you do not activate this setting, it will be the PHP script that handles the redirection to existing webp files. Best performance is achieved by redirecting in .htaccess, however the feature is new and has not been tested widely. Please try it out, and report any problems in the forum, thanks :)<br>Beware that there is currently no hooks for regenerating converted images when source image is edited.');
6
- echo '</th><td>';
7
- echo '<input type="checkbox" id="redirect_to_existing_in_htaccess" name="redirect-to-existing-in-htaccess" value="true" ' . ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '') . '">';
8
- echo '</td></tr>';
 
 
 
 
 
 
 
 
lib/options/options/redirection-rules/add-vary-header-in-htaccess.inc ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ We do not need this setting currently.
3
+ We will rather let this be decided by the success-response option
4
+ <tr>
5
+ <th scope="row">
6
+ Add Vary:Accept Header for the images <?php echo helpIcon('The Vary:Accept header tells browsers (and CDNs) that the response depends on the Accept header (which is the header that browsers use to indicate that they accept webp images). Usually you should leave this on. But if you are using the Cache Enabler plugin, check it off. '); ?>
7
+ </th>
8
+ <td>
9
+ <input type="checkbox" id="add_vary_header_in_htaccess" name="add-vary-header-in-htaccess" value="true" <?php echo ($config['add-vary-header-in-htaccess'] ? 'checked="checked"' : '') ?> >
10
+ </td>
11
+ </tr>
12
+ -->
lib/options/options/redirection-rules/do-not-pass-source-path-in-query-string.inc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ echo '<tr><th scope="row">Do not pass source in Query String';
3
+ echo helpIcon('You can try unchecking this, if you are experiencing that no images are converted. In v0.8 and below, the <i>.htaccess</i> always passed the filename of the image to the script through the query string. It however seems that passing through an environment variable instead works just fine. As passing it through the query string can cause some firewalls to block the request, we no longer do this per default.');
4
+ echo '</th><td>';
5
+ echo '<input type="checkbox" id="do_not_pass_source_in_query_string" name="do-not-pass-source-in-query-string" value="true" ' . ($config['do-not-pass-source-in-query-string'] ? 'checked="checked"' : '') . '">';
6
+ echo '</td></tr>';
lib/options/options/redirection-rules/enable-redirection-to-converter.inc ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <th scope="row">
3
+ <?php if ($config['operation-mode'] == 'just-convert'): ?>
4
+ Auto convert <?php echo helpIcon('<p><em>The auto-convertion works this way: When an image is requested, a rule in the .htaccess detects if that image has been converted. If not, the request is redirected to the converter, which creates the webp and returns <em>the original</em> image</em></p>'); ?>
5
+ <?php else: ?>
6
+ Enable redirection to converter?<?php echo helpIcon('This will add rules in the .htaccess that redirects to converter'); ?>
7
+ <?php endif; ?>
8
+ </th>
9
+ <td>
10
+ <input
11
+ id="enable_redirection_to_converter"
12
+ name="enable-redirection-to-converter"
13
+ <?php echo ($config['enable-redirection-to-converter'] ? 'checked="checked"' : '') ?>
14
+ value="true"
15
+ type="checkbox"
16
+ >
17
+ </td>
18
+ </tr>
lib/options/options/redirection-rules/image-types.inc ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Image types
3
+ // ------------
4
+ echo '<tr><th scope="row">';
5
+ switch ($config['operation-mode']) {
6
+ case 'standard':
7
+ echo 'Image types to work on';
8
+ break;
9
+ case 'just-convert':
10
+ echo 'Image types to convert';
11
+ break;
12
+ case 'just-redirect':
13
+ echo 'Image types to redirect';
14
+ break;
15
+ case 'tweaked':
16
+ echo 'Image types to send to the converter';
17
+ break;
18
+ }
19
+ echo helpIcon('<p>Beware that the Gd conversion method cannot handle transparency for PNGs. PNG conversions havent been tested much yet. Please report any problems with PNG images <a target="_blank" href="https://github.com/rosell-dk/webp-convert/issues/42">here</a></p><p>The term "send" means an internal redirect on the server (not to be confused with an external redirect, such as a 301 or 302 response)</p>');
20
+ echo '</th><td>';
21
+
22
+ // bitmask
23
+ // 1: JPEGs
24
+ // 2: PNG's
25
+ // Converting only jpegs is thus "1"
26
+ // Converting both jpegs and pngs is (1+2) = 3
27
+ //$imageTypes = get_option('webp_express_image_types_to_convert');
28
+ $imageTypes = $config['image-types'];
29
+
30
+ echo '<select name="image-types">';
31
+ echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>None! (disable)</option>';
32
+ echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only jpegs</option>';
33
+ echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Both jpegs and pngs</option>';
34
+ echo '</select>';
35
+
36
+ echo '</td></tr>';
lib/options/options/redirection-rules/only-redirect-to-converter-for-webp-enabled-browsers.inc ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <th scope="row">
3
+ Only redirect to converter for webp-enabled browsers?<?php echo helpIcon('If checked, a condition is added to the .htaccess, that the <i>Accept</i> header contains "image/webp"'); ?>
4
+ </th>
5
+ <td>
6
+ <input
7
+ name="only-redirect-to-converter-for-webp-enabled-browsers"
8
+ <?php echo ($config['only-redirect-to-converter-for-webp-enabled-browsers'] ? 'checked="checked"' : '') ?>
9
+ value="true"
10
+ type="checkbox"
11
+ >
12
+ </td>
13
+ </tr>
lib/options/options/redirection-rules/only-redirect-to-converter-on-cache-miss.inc ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <th scope="row">
3
+ Only redirect to converter if no webp is found in cache <?php echo helpIcon('This will add a condition to the RewriteRule that redirects to the converter. Useful if you use WebP Express together with the Cache Enabler plugin.'); ?>
4
+ </th>
5
+ <td>
6
+ <input type="checkbox" name="only-redirect-to-converter-on-cache-miss" value="true" <?php echo ($config['only-redirect-to-converter-on-cache-miss'] ? 'checked="checked"' : '') ?> >
7
+ </td>
8
+ </tr>
lib/options/options/redirection-rules/redirect-to-existing.inc ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <th scope="row">
3
+ Redirect directly to converted image when available<?php
4
+ echo helpIcon('This will add rules in the .htaccess that redirects directly to existing converted files. ' .
5
+ 'If you do not activate this setting, it will be the PHP script that handles the redirection to existing ' .
6
+ 'webp files. Best performance is achieved by redirecting in .htaccess'); ?>
7
+ </th>
8
+ <td>
9
+ <input type="checkbox" id="redirect_to_existing_in_htaccess" name="redirect-to-existing-in-htaccess" value="true" <?php echo ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '') ?> >
10
+ </td>
11
+ </tr>
lib/options/options/redirection-rules/redirection-rules.inc ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($config['operation-mode'] == 'tweaked') : ?>
2
+ <fieldset class="block">
3
+ <h3>Redirection rules</h3>
4
+ <div><i>The options here affects the rules created in the .htaccess. <?php echo helpIcon('And so does some other options. If "Redirect directly to converted image" is set, the "Destination folder" and "File Extension" and "Caching" options will be used'); ?></i></div>
5
+ <table class="form-table">
6
+ <tbody>
7
+ <?php
8
+ include_once 'enable-redirection-to-converter.inc';
9
+ include_once 'only-redirect-to-converter-for-webp-enabled-browsers.inc';
10
+ include_once 'image-types.inc';
11
+ include_once 'do-not-pass-source-path-in-query-string.inc';
12
+ include_once 'only-redirect-to-converter-on-cache-miss.inc';
13
+ include_once 'redirect-to-existing.inc';
14
+ ?>
15
+ </tbody>
16
+ </table>
17
+ </fieldset>
18
+ <?php
19
+ else:
20
+ if ($config['operation-mode'] == 'just-convert') {
21
+ // ps: we call it "auto convert", when in this mode
22
+ // PPS: we include it directly in page.php now.
23
+ //include_once 'enable-redirection-to-converter.inc';
24
+
25
+ //include_once 'only-redirect-to-converter-on-cache-miss.inc';
26
+
27
+ }
28
+ if ($config['operation-mode'] == 'standard') {
29
+ include_once 'redirect-to-existing.inc';
30
+ }
31
+ include_once 'image-types.inc';
32
+ endif;
33
+ ?>
lib/options/options/serve-options/cache-control.inc ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //$maxQuality = get_option('webp_express_max_quality');
4
+ $cacheControl = $config['cache-control'];
5
+ $cacheControlCustom = $config['cache-control-custom'];
6
+ $cacheControlMaxAge = $config['cache-control-max-age'];
7
+ $cacheControlPublic = $config['cache-control-public'];
8
+ ?>
9
+
10
+ <tr>
11
+ <th scope="row">Cache-Control header <?php echo helpIcon(
12
+ '<p>Controls the cache-control header on successful conversion and direct redirection to converted ' .
13
+ 'image in .htaccess. In case of convert failure, headers will be sent to prevent caching.</p>' .
14
+ '<p>PS: In order to set <i>stale-while-revalidate</i> and <i>stale-if-error directives<i>, you must ' .
15
+ 'currently choose "Custom". <a target="_blank" href="https://www.fastly.com/blog/stale-while-revalidate-stale-if-error-available-today">It is a good idea to set these</a>.' .
16
+ '</p>');
17
+ ?>
18
+ </th>
19
+ <td>
20
+ <select id="cache_control_select" name="cache-control">
21
+ <option value="no-header" <?php if ($cacheControl == 'no-header') echo ' selected' ?>>Do not set</option>
22
+ <option value="set" <?php if ($cacheControl == 'set') echo ' selected' ?>>Set</option>
23
+ <option value="custom" <?php if ($cacheControl == 'custom') echo ' selected' ?>>Custom</option>
24
+ <?php
25
+ /*
26
+ echo '<option value="no-header"' . ($cacheControl == 'no-header' ? ' selected' : '') . '>Do not set Cache-Control header</option>';
27
+ echo '<option value="one-second"' . ($cacheControl == 'one-second' ? ' selected' : '') . '>One second</option>';
28
+ echo '<option value="one-minute"' . ($cacheControl == 'one-minute' ? ' selected' : '') . '>One minute</option>';
29
+ echo '<option value="one-hour"' . ($cacheControl == 'one-hour' ? ' selected' : '') . '>One hour</option>';
30
+ echo '<option value="one-day"' . ($cacheControl == 'one-day' ? ' selected' : '') . '>One day</option>';
31
+ echo '<option value="one-week"' . ($cacheControl == 'one-week' ? ' selected' : '') . '>One week</option>';
32
+ echo '<option value="one-month"' . ($cacheControl == 'one-month' ? ' selected' : '') . '>One month</option>';
33
+ echo '<option value="one-year"' . ($cacheControl == 'one-year' ? ' selected' : '') . '>One year</option>';
34
+ echo '<option value="custom"' . ($cacheControl == 'custom' ? ' selected' : '') . '>Custom Cache-Control header</option>';
35
+ */
36
+ ?>
37
+ </select>
38
+ <div id="cache_control_custom_div" style="display:inline-block;">
39
+ <input type="text" id="cache_control_custom" name="cache-control-custom" value="<?php echo $cacheControlCustom ?>">
40
+ <?php echo helpIcon(
41
+ 'You can read about possible options ' .
42
+ '<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control">here</a>',
43
+ 'no-margin-left'
44
+ );?>
45
+ </div>
46
+
47
+ <div id="cache_control_set_div" style="display:inline-block;">
48
+ <select id="cache_control_public" name="cache-control-public">
49
+ <?php
50
+ webpexpress_selectBoxOptions(($cacheControlPublic ? 'public' : 'private'), [
51
+ 'public' => 'Public',
52
+ 'private' => 'Private',
53
+ ]);
54
+ ?>
55
+ </select>
56
+ <?php echo helpIcon(
57
+ '<p>Set either the "public" or "private" directive. Setting this to public means that you are allow caching in shared caches. ' .
58
+ 'Only do this, if you are sure your CDN or reverse proxy can handle that the ' .
59
+ 'response varies depending on the Accept header.</p>' .
60
+ '<p>Note: I am not completely sure that all forward proxies handles varied responses. ' .
61
+ 'This is discussed <a target="_blank" href="https://github.com/rosell-dk/webp-express/issues/144">here</a>.</p>'
62
+ ,
63
+ 'no-margin-left set-margin-right'
64
+ );
65
+ ?>
66
+ </select>
67
+ <select id="cache_control_max_age" name="cache-control-max-age">
68
+ <?php
69
+ webpexpress_selectBoxOptions($cacheControlMaxAge, [
70
+ 'one-second' => 'One second',
71
+ 'one-minute' => 'One minute',
72
+ 'one-hour' => 'One hour',
73
+ 'one-day' => 'One day',
74
+ 'one-week' => 'One week',
75
+ 'one-month' => 'One month',
76
+ 'one-year' => 'One year',
77
+ ]);
78
+ ?>
79
+ </select>
80
+ <?php echo helpIcon(
81
+ 'This sets the max-age value. If want to set s-maxage, or generally need more control, ' .
82
+ 'choose "custom" in the first combobox (You can read about possible options ' .
83
+ '<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control">here</a>)',
84
+ 'no-margin-left'
85
+ );?>
86
+ </div>
87
+ <br>
88
+ <!--
89
+ <table>
90
+ <tbody>
91
+ <tr>
92
+ <td style="font-weight: bold; margin:0; padding:0">max-age</td>
93
+ <td>
94
+ </td>
95
+ </tr>
96
+ <tr>
97
+ <th scope="row">
98
+ Allow caching in shared caches (ie CDNs or reverse proxies)
99
+ <?php echo helpIcon(''); ?>
100
+ </th>
101
+ <td>
102
+ <input
103
+ name="cache-control-public"
104
+ <?php echo ($config['cache-control-public'] ? 'checked="checked"' : '') ?>
105
+ value="true"
106
+ type="checkbox"
107
+ >
108
+ </td>
109
+ </tr>
110
+ </tbody>
111
+ </table>
112
+ -->
113
+
114
+ </td>
115
+ </tr>
lib/options/options/{response-on-failure.inc → serve-options/response-on-failure.inc} RENAMED
File without changes
lib/options/options/serve-options/response-on-success.inc ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ echo '<tr><th scope="row">Response on success';
3
+ echo helpIcon('<p>Determines what to serve when conversion is a success. If you are using the Cache Enabler plugin, set to "Original image", otherwise you would normally set it to "Converted image".</p><p>If set to "Converted image", a Vary:Accept header will be sent to indicate that the response depends on the Accept header (which indicates if a browser supports webp images or not)</p><p>If set to "Original image", make sure to disable the "Redirect directly to converted image when available" option in the Redirect rules</p>');
4
+ echo '</th><td>';
5
+
6
+ //$successResponse = get_option('webp_express_failure_response');
7
+ $successResponse = $config['success-response'];
8
+ echo '<select name="success-response">';
9
+ echo '<option value="original"' . ($successResponse == 'original' ? ' selected' : '') . '>Original image</option>';
10
+ echo '<option value="converted"' . ($successResponse == 'converted' ? ' selected' : '') . '>Converted image</option>';
11
+ echo '</select>';
12
+ echo '</td></tr>';
lib/options/options/serve-options/serve-options.inc ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($config['operation-mode'] == 'tweaked') : ?>
2
+ <fieldset class="block">
3
+ <h3>Serve options</h3>
4
+ <p><i>The options here affects how the image is served after a successful / unsuccessful conversion</i></p>
5
+ <table class="form-table">
6
+ <tbody>
7
+ <?php
8
+ include_once 'cache-control.inc';
9
+ include_once 'response-on-failure.inc';
10
+ include_once 'response-on-success.inc';
11
+ ?>
12
+ </tbody>
13
+ </table>
14
+ </fieldset>
15
+ <?php
16
+ else:
17
+ if ($config['operation-mode'] != 'just-convert') {
18
+ include_once 'cache-control.inc';
19
+ }
20
+ endif;
21
+ ?>
lib/options/options/web-service-options/web-service-options.inc ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($config['operation-mode'] == 'tweaked') : ?>
2
+ <fieldset class="block">
3
+ <h3>Web service</h3>
4
+ <table class="form-table">
5
+ <tbody>
6
+ <?php
7
+ include_once 'web-service.inc';
8
+ ?>
9
+ </tbody>
10
+ </table>
11
+ </fieldset>
12
+ <?php
13
+ else:
14
+ include_once 'web-service.inc';
15
+ endif;
16
+ ?>
lib/options/options/{web-service.inc → web-service-options/web-service.inc} RENAMED
@@ -1,5 +1,5 @@
1
  <?php
2
- include_once __DIR__ . '/../../classes/Paths.php';
3
  use \WebPExpress\Paths;
4
 
5
  //$whitelist = $config['web-service']['whitelist'];
1
  <?php
2
+ include_once __DIR__ . '/../../../classes/Paths.php';
3
  use \WebPExpress\Paths;
4
 
5
  //$whitelist = $config['web-service']['whitelist'];
lib/options/page-messages.php CHANGED
@@ -40,7 +40,7 @@ if (!Paths::createContentDirIfMissing()) {
40
  Messenger::printMessage(
41
  'error',
42
  'WebP Express needs to create a directory "webp-express" under your wp-content folder, but does not have permission to do so.<br>' .
43
- 'Please create the folder manually, or change the file permissions of your wp-content folder.'
44
  );
45
  } else {
46
  if (!Paths::createConfigDirIfMissing()) {
40
  Messenger::printMessage(
41
  'error',
42
  'WebP Express needs to create a directory "webp-express" under your wp-content folder, but does not have permission to do so.<br>' .
43
+ 'Please create the folder manually, or change the file permissions of your wp-content folder (failed to create this folder: ' . Paths::getContentDirAbs() . ')'
44
  );
45
  } else {
46
  if (!Paths::createConfigDirIfMissing()) {
lib/options/page.php CHANGED
@@ -75,7 +75,7 @@ if (!$testResult) {
75
  'error',
76
  'WebP Express cannot save a test conversion, because it does not have write ' .
77
  'access to your upload folder, nor your wp-content folder. Please provide!'
78
- );
79
  }
80
 
81
 
@@ -94,19 +94,9 @@ foreach (Paths::getHTAccessDirs() as $dir) {
94
  // Generate a custom nonce value.
95
  $webpexpress_settings_nonce = wp_create_nonce('webpexpress_settings_nonce');
96
  ?>
97
- <p><div>
98
- <i>WebP Express takes care of serving autogenerated WebP images instead of jpeg/png to browsers that supports WebP.</i>
99
- <div class="help">?<div class="popup"><ol>
100
- <li>Some redirect rules set up in <i>.htaccess</i> redirects (unconverted) jpeg/png images to a PHP script for handling.</li>
101
- <li>The PHP script reads the options and passes them to the <i><a target="_blank" href="https://github.com/rosell-dk/webp-convert/">WebP Convert</a></i> library for converting <i>and</i> serving.</li>
102
- <li>If WebP Convert finds that the image already is converted, it will be served immediately (unless it is bigger than the original, or the original has been modified). Otherwise it will be converted and then served</li>
103
- </ol></div></div>
104
- </div></p>
105
 
106
  <?php
107
 
108
-
109
-
110
  echo '<form id="webpexpress_settings" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" method="post" >';
111
  ?>
112
  <input type="hidden" name="action" value="webpexpress_settings_submit">
@@ -121,64 +111,59 @@ echo '<form id="webpexpress_settings" action="' . esc_url( admin_url( 'admin-pos
121
  </table>
122
  </fieldset>
123
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- function helpIcon($text) {
126
- return '<div class="help">?<div class="popup">' . $text . '</div></div>';
 
 
 
 
127
  }
128
- ?>
129
 
130
- <p>
131
-
132
-
133
-
134
- <fieldset class="block">
135
- <h3>Redirection rules</h3>
136
- <p><i>The options here affects the rules created in the .htaccess.</i></p>
137
- <table class="form-table">
138
- <tbody>
139
- <?php
140
- include_once 'options/image-types.inc';
141
- include_once 'options/redirect-to-existing.inc';
142
- include_once 'options/do-not-pass-source-path-in-query-string.inc';
143
- ?>
144
- </tbody>
145
- </table>
146
- </fieldset>
147
- <fieldset class="block">
148
- <h3>Conversion options</h3>
149
- <p><i>The options here affects the conversion process</i></p>
150
- <table class="form-table">
151
- <tbody>
152
- <?php
153
- include_once 'options/quality.inc';
154
- include_once 'options/metadata.inc';
155
- include_once 'options/converters.inc';
156
- ?>
157
- </tbody>
158
- </table>
159
- </fieldset>
160
- <fieldset class="block">
161
- <h3>Serve options</h3>
162
- <p><i>The options here affects how the image is served after a successful / unsuccessful conversion</i></p>
163
- <table class="form-table">
164
- <tbody>
165
- <?php
166
- include_once 'options/cache-control.inc';
167
- include_once 'options/response-on-failure.inc';
168
- ?>
169
- </tbody>
170
- </table>
171
- </fieldset>
172
- <fieldset class="block">
173
- <h3>Web service</h3>
174
- <table class="form-table">
175
- <tbody>
176
- <?php
177
- include_once 'options/web-service.inc';
178
- ?>
179
- </tbody>
180
- </table>
181
- </fieldset>
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  </form>
184
  </div>
75
  'error',
76
  'WebP Express cannot save a test conversion, because it does not have write ' .
77
  'access to your upload folder, nor your wp-content folder. Please provide!'
78
+ );
79
  }
80
 
81
 
94
  // Generate a custom nonce value.
95
  $webpexpress_settings_nonce = wp_create_nonce('webpexpress_settings_nonce');
96
  ?>
 
 
 
 
 
 
 
 
97
 
98
  <?php
99
 
 
 
100
  echo '<form id="webpexpress_settings" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" method="post" >';
101
  ?>
102
  <input type="hidden" name="action" value="webpexpress_settings_submit">
111
  </table>
112
  </fieldset>
113
  <?php
114
+ function helpIcon($text, $customClass = '') {
115
+ $className = '';
116
+ if (strlen($text) < 80) {
117
+ $className = 'narrow';
118
+ }
119
+ if (strlen($text) > 150) {
120
+ if (strlen($text) > 300) {
121
+ $className = 'wider';
122
+ } else {
123
+ $className = 'wide';
124
+ }
125
+ }
126
+ return '<div class="help ' . $customClass . '">?<div class="popup ' . $className . '">' . $text . '</div></div>';
127
+ }
128
 
129
+ function webpexpress_selectBoxOptions($selected, $options) {
130
+ foreach ($options as $optionValue => $text) {
131
+ echo '<option value="' . $optionValue . '"' . ($optionValue == $selected ? ' selected' : '') . '>';
132
+ echo $text;
133
+ echo '</option>';
134
+ }
135
  }
 
136
 
137
+ include_once 'options/operation-mode.inc';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ if ($config['operation-mode'] != 'tweaked') {
140
+ echo '<fieldset class="block">';
141
+ echo '<table class="form-table"><tbody>';
142
+ }
143
+
144
+ include_once 'options/redirection-rules/redirection-rules.inc';
145
+ if ($config['operation-mode'] != 'just-redirect') {
146
+ include_once 'options/conversion-options/conversion-options.inc';
147
+ }
148
+ if ($config['operation-mode'] == 'just-redirect') {
149
+ include_once 'options/conversion-options/destination-extension.inc';
150
+ }
151
+ include_once 'options/serve-options/serve-options.inc';
152
+
153
+ if ($config['operation-mode'] == 'just-convert') {
154
+ // ps: we call it "auto convert", when in this mode
155
+ include_once 'options/redirection-rules/enable-redirection-to-converter.inc';
156
+ }
157
+
158
+ if ($config['operation-mode'] != 'just-redirect') {
159
+ include_once 'options/web-service-options/web-service-options.inc';
160
+ }
161
+
162
+ if ($config['operation-mode'] != 'tweaked') {
163
+ echo '</tbody></table>';
164
+ echo '</fieldset>';
165
+ }
166
+
167
+ ?>
168
  </form>
169
  </div>
lib/options/submit.php CHANGED
@@ -1,9 +1,11 @@
1
  <?php
2
 
 
 
 
3
  include_once __DIR__ . '/../classes/Config.php';
4
  use \WebPExpress\Config;
5
 
6
-
7
  include_once __DIR__ . '/../classes/HTAccess.php';
8
  use \WebPExpress\HTAccess;
9
 
@@ -13,82 +15,6 @@ use \WebPExpress\Messenger;
13
  include_once __DIR__ . '/../classes/Paths.php';
14
  use \WebPExpress\Paths;
15
 
16
- /**
17
- * Generate valid salt for blowfish, using string that
18
- * may contain invalid characters.
19
- * The string supplied should preferably be at least 22 chars, but may be less
20
- */
21
- function webp_express_generateBlowfishSalt($string) {
22
- // http://php.net/manual/en/function.crypt.php
23
-
24
- // Salt may only contain the following characters: "./0-9A-Za-z"
25
- $salt = preg_replace('/([^a-zA-Z0-9.\\/])/', '', $string);
26
-
27
- // Salt must be at least 22 chars
28
- while (strlen($salt) < 22) {
29
- $salt .= strtoupper($salt);
30
- }
31
-
32
- // It seems salt may be more than 22. But not sure. We trim
33
- $salt = substr($salt, 0, 22);
34
-
35
- return $salt;
36
- }
37
-
38
- function webp_express_hashItForMe($password) {
39
-
40
- if (CRYPT_BLOWFISH == 1) {
41
- $salt = webp_express_generateBlowfishSalt('./aATesting.123');
42
- $crypted = crypt($password, '$2y$10$' . $salt . '$');
43
-
44
- // No reason to store the first 28 character.
45
- // The first 6 are always "$2y$10$". The next 22 is the salt, which we cannot allow to be
46
- // random, because...
47
-
48
- // Hm. perhaps, instead of a whitelist, we could have a list of authorized clients
49
- // Procedure:
50
- /*
51
- On server:
52
- - Click "Listen for requests".
53
- That opens a dialog showing the URL that it is listening on
54
-
55
- On client:
56
- - Click "Send request". That opens a prompt for the URL
57
- - Enter URL
58
- - Client sends a request including a autogenerated password (crypted?).
59
- The salt is included in the crypted password (crypt() output)
60
- Blowfish is specified.
61
- Perhaps also a callback URL, so client can display message upon connection.
62
- But a "test connection" link would probably suffice
63
-
64
- On server:
65
- - The request is displayed (polling, including the client domain / IP
66
- - Click "Accept" next to the request.
67
- - The request is added to the authorized list (IP, domain? and the crypted password)
68
- - The client
69
-
70
- On client:
71
- - When request
72
- */
73
- return substr($crypted, 28);
74
- }
75
-
76
- /*
77
- $salt = 'banana';
78
- // TODO: Lets use the server URL (at the time of password creation) as salt
79
- // This will work, even if site changes URL (not if new sites are connecting,
80
- // to same though)
81
-
82
- if (function_exists('md5')) {
83
- return md5($password . $salt);
84
- }
85
- */
86
- // No, we cannot use password_hash.
87
- // We need something that returns the same hash on server and client.
88
- // So we need to specify salt, and ensure it is the same
89
- //password_hash($entry['new_password'], PASSWORD_DEFAULT);
90
- }
91
-
92
  // https://premium.wpmudev.org/blog/handling-form-submissions/
93
  // checkout https://codex.wordpress.org/Function_Reference/sanitize_meta
94
 
@@ -99,45 +25,65 @@ function webp_express_sanitize_quality_field($text) {
99
  $q = round($q);
100
  return max(0, min($q, 100));
101
  }
102
- $config = [
103
- 'cache-control' => sanitize_text_field($_POST['cache-control']),
104
- 'cache-control-custom' => sanitize_text_field($_POST['cache-control-custom']),
105
- 'converters' => json_decode(wp_unslash($_POST['converters']), true), // holy moly! - https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
106
- 'fail' => sanitize_text_field($_POST['fail']),
 
 
 
 
107
  'image-types' => sanitize_text_field($_POST['image-types']),
108
- 'metadata' => sanitize_text_field($_POST['metadata']),
109
  'forward-query-string' => true,
110
- 'do-not-pass-source-in-query-string' => isset($_POST['do-not-pass-source-in-query-string']),
111
- 'redirect-to-existing-in-htaccess' => isset($_POST['redirect-to-existing-in-htaccess']),
112
- 'web-service' => [
113
- 'enabled' => isset($_POST['web-service-enabled']),
114
- 'whitelist' => json_decode(wp_unslash($_POST['whitelist']), true)
115
- ]
116
- ];
117
 
118
- $auto = (isset($_POST['quality-auto']) && $_POST['quality-auto'] == 'auto_on');
119
- $config['quality-auto'] = $auto;
120
-
121
- if ($auto) {
122
- $config['max-quality'] = webp_express_sanitize_quality_field($_POST['max-quality']);
123
- $config['quality-specific'] = 70;
124
- } else {
125
- $config['max-quality'] = 80;
126
- $config['quality-specific'] = webp_express_sanitize_quality_field($_POST['quality-specific']);
 
 
 
 
 
 
 
127
  }
128
 
129
- //echo '<pre>' . print_r($config['converters'], true) . '</pre>';
130
- //die;
131
 
132
- // remove id's
133
- foreach ($config['converters'] as &$converter) {
134
- unset ($converter['id']);
135
- }
136
 
137
- $oldConfig = Config::loadConfig();
 
 
 
138
 
139
- // Set existing api keys in web service (we removed them from the json array, for security purposes)
140
- if ($oldConfig !== false) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  if (isset($oldConfig['web-service']['whitelist'])) {
142
  foreach ($oldConfig['web-service']['whitelist'] as $existingWhitelistEntry) {
143
  foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
@@ -147,19 +93,27 @@ if ($oldConfig !== false) {
147
  }
148
  }
149
  }
150
- }
151
 
152
- // Set new api keys in web service
153
- foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
154
- if (!empty($whitelistEntry['new-api-key'])) {
155
- $whitelistEntry['api-key'] = $whitelistEntry['new-api-key'];
156
- unset($whitelistEntry['new-api-key']);
 
157
  }
158
- }
159
 
160
- // Get existing wpc api key from old config
161
- $existingWpcApiKey = '';
162
- if ($oldConfig !== false) {
 
 
 
 
 
 
 
 
 
163
  foreach ($oldConfig['converters'] as &$converter) {
164
  if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
165
  if (isset($converter['options']['api-key'])) {
@@ -167,59 +121,62 @@ if ($oldConfig !== false) {
167
  }
168
  }
169
  }
170
- }
171
 
172
- // Set wpc api key in new config
173
- // - either to the existing, or to a new
174
- foreach ($config['converters'] as &$converter) {
175
- if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
176
- unset($converter['options']['_api-key-non-empty']);
177
- if (isset($converter['options']['new-api-key'])) {
178
- $converter['options']['api-key'] = $converter['options']['new-api-key'];
179
- unset($converter['options']['new-api-key']);
180
- } else {
181
- $converter['options']['api-key'] = $existingWpcApiKey;
 
182
  }
183
  }
184
  }
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
- // create password hashes for new passwords
188
- /*
189
- foreach ($config['server']['whitelist'] as &$entry) {
190
- if (!empty($entry['new_password'])) {
191
-
192
- if ($entry['hash_new_password']) {
193
- $entry['password'] = webp_express_hashItForMe($entry['new_password']);
194
- } else {
195
- $entry['password'] = $entry['new_password'];
196
- }
197
-
198
- unset($entry['hash_new_password']);
199
- unset($entry['new_password']);
200
- }
201
  }
202
- */
203
 
 
 
204
  $result = Config::saveConfigurationAndHTAccess($config, isset($_POST['force']));
205
 
206
- /*
207
- Messenger::addMessage(
208
- 'info',
209
- isset($_POST['force']) ? 'force' : 'no-force' .
210
- (HTAccess::doesRewriteRulesNeedUpdate($config) ? 'need' : 'no need')
211
- );*/
212
-
213
- /*
214
- Messenger::addMessage(
215
- 'info',
216
- '<pre>' . htmlentities(print_r($config, true)) . '</pre>'
217
- );
218
 
219
- Messenger::addMessage(
220
- 'info',
221
- '<pre>' . htmlentities(print_r($result, true)) . '</pre>'
222
- );*/
223
 
224
  if (!$result['saved-both-config']) {
225
  if (!$result['saved-main-config']) {
@@ -237,6 +194,52 @@ if (!$result['saved-both-config']) {
237
 
238
  }
239
  } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  if (!$result['rules-needed-update']) {
241
  Messenger::addMessage(
242
  'success',
1
  <?php
2
 
3
+ include_once __DIR__ . '/../classes/CacheMover.php';
4
+ use \WebPExpress\CacheMover;
5
+
6
  include_once __DIR__ . '/../classes/Config.php';
7
  use \WebPExpress\Config;
8
 
 
9
  include_once __DIR__ . '/../classes/HTAccess.php';
10
  use \WebPExpress\HTAccess;
11
 
15
  include_once __DIR__ . '/../classes/Paths.php';
16
  use \WebPExpress\Paths;
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  // https://premium.wpmudev.org/blog/handling-form-submissions/
19
  // checkout https://codex.wordpress.org/Function_Reference/sanitize_meta
20
 
25
  $q = round($q);
26
  return max(0, min($q, 100));
27
  }
28
+
29
+ $config = Config::loadConfigAndFix();
30
+ $oldConfig = $config;
31
+
32
+ // Set options that are available in all operation modes
33
+ $config = array_merge($config, [
34
+ 'operation-mode' => $_POST['operation-mode'],
35
+
36
+ // redirection rules
37
  'image-types' => sanitize_text_field($_POST['image-types']),
 
38
  'forward-query-string' => true,
 
 
 
 
 
 
 
39
 
40
+ // serve options
41
+ 'cache-control' => sanitize_text_field($_POST['cache-control']),
42
+ 'cache-control-custom' => sanitize_text_field($_POST['cache-control-custom']),
43
+ ]);
44
+
45
+ $cacheControl = sanitize_text_field($_POST['cache-control']);
46
+ switch ($cacheControl) {
47
+ case 'no-header':
48
+ break;
49
+ case 'set':
50
+ $config['cache-control-max-age'] = sanitize_text_field($_POST['cache-control-max-age']);
51
+ $config['cache-control-public'] = (sanitize_text_field($_POST['cache-control-public']) == 'public');
52
+ break;
53
+ case 'custom':
54
+ $config['cache-control-custom'] = sanitize_text_field($_POST['cache-control-custom']);
55
+ break;
56
  }
57
 
58
+ // Set options that are available in all operation modes, except the "just-redirect" mode
59
+ if ($_POST['operation-mode'] != 'just-redirect') {
60
 
61
+ // Metadata
62
+ // --------
63
+ $config['metadata'] = sanitize_text_field($_POST['metadata']);
 
64
 
65
+ // Quality
66
+ // --------
67
+ $auto = (isset($_POST['quality-auto']) && $_POST['quality-auto'] == 'auto_on');
68
+ $config['quality-auto'] = $auto;
69
 
70
+ if ($auto) {
71
+ $config['max-quality'] = webp_express_sanitize_quality_field($_POST['max-quality']);
72
+ $config['quality-specific'] = 70;
73
+ } else {
74
+ $config['max-quality'] = 80;
75
+ $config['quality-specific'] = webp_express_sanitize_quality_field($_POST['quality-specific']);
76
+ }
77
+
78
+ // Web Service
79
+ // -------------
80
+
81
+ $config['web-service'] = [
82
+ 'enabled' => isset($_POST['web-service-enabled']),
83
+ 'whitelist' => json_decode(wp_unslash($_POST['whitelist']), true)
84
+ ];
85
+
86
+ // Set existing api keys in web service (we removed them from the json array, for security purposes)
87
  if (isset($oldConfig['web-service']['whitelist'])) {
88
  foreach ($oldConfig['web-service']['whitelist'] as $existingWhitelistEntry) {
89
  foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
93
  }
94
  }
95
  }
 
96
 
97
+ // Set new api keys in web service
98
+ foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
99
+ if (!empty($whitelistEntry['new-api-key'])) {
100
+ $whitelistEntry['api-key'] = $whitelistEntry['new-api-key'];
101
+ unset($whitelistEntry['new-api-key']);
102
+ }
103
  }
 
104
 
105
+ // Converters
106
+ // -------------
107
+
108
+ $config['converters'] = json_decode(wp_unslash($_POST['converters']), true); // holy moly! - https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
109
+
110
+ // remove converter ids
111
+ foreach ($config['converters'] as &$converter) {
112
+ unset ($converter['id']);
113
+ }
114
+
115
+ // Get existing wpc api key from old config
116
+ $existingWpcApiKey = '';
117
  foreach ($oldConfig['converters'] as &$converter) {
118
  if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
119
  if (isset($converter['options']['api-key'])) {
121
  }
122
  }
123
  }
 
124
 
125
+ // Set wpc api key in new config
126
+ // - either to the existing, or to a new
127
+ foreach ($config['converters'] as &$converter) {
128
+ if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
129
+ unset($converter['options']['_api-key-non-empty']);
130
+ if (isset($converter['options']['new-api-key'])) {
131
+ $converter['options']['api-key'] = $converter['options']['new-api-key'];
132
+ unset($converter['options']['new-api-key']);
133
+ } else {
134
+ $converter['options']['api-key'] = $existingWpcApiKey;
135
+ }
136
  }
137
  }
138
  }
139
 
140
+ switch ($_POST['operation-mode']) {
141
+ case 'standard':
142
+ $config = array_merge($config, [
143
+ 'redirect-to-existing-in-htaccess' => isset($_POST['redirect-to-existing-in-htaccess']),
144
+ ]);
145
+ break;
146
+ case 'just-convert':
147
+ $config = array_merge($config, [
148
+ 'destination-extension' => $_POST['destination-extension'],
149
+ 'enable-redirection-to-converter' => isset($_POST['enable-redirection-to-converter']), // PS: its called "autoconvert" in this mode
150
+ ]);
151
+ break;
152
+ case 'tweaked':
153
+ $config = array_merge($config, [
154
+ 'enable-redirection-to-converter' => isset($_POST['enable-redirection-to-converter']),
155
+ 'only-redirect-to-converter-for-webp-enabled-browsers' => isset($_POST['only-redirect-to-converter-for-webp-enabled-browsers']),
156
+ 'only-redirect-to-converter-on-cache-miss' => isset($_POST['only-redirect-to-converter-on-cache-miss']),
157
+ 'do-not-pass-source-in-query-string' => isset($_POST['do-not-pass-source-in-query-string']),
158
+ 'redirect-to-existing-in-htaccess' => isset($_POST['redirect-to-existing-in-htaccess']),
159
+ 'destination-folder' => $_POST['destination-folder'],
160
+ 'destination-extension' => (($_POST['destination-folder'] == 'mingled') ? $_POST['destination-extension'] : 'append'),
161
+ 'fail' => sanitize_text_field($_POST['fail']),
162
+ 'success-response' => sanitize_text_field($_POST['success-response']),
163
+ ]);
164
+ break;
165
+ }
166
 
167
+ //echo '<pre>' . print_r($_POST, true) . '</pre>'; exit;
168
+ if ($_POST['operation-mode'] != $_POST['change-operation-mode']) {
169
+ $config['operation-mode'] = $_POST['change-operation-mode'];
170
+ $config = Config::applyOperationMode($config);
 
 
 
 
 
 
 
 
 
 
171
  }
 
172
 
173
+ // SAVE!
174
+ // -----
175
  $result = Config::saveConfigurationAndHTAccess($config, isset($_POST['force']));
176
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
+ // Handle results
179
+ // ---------------
 
 
180
 
181
  if (!$result['saved-both-config']) {
182
  if (!$result['saved-main-config']) {
194
 
195
  }
196
  } else {
197
+ if (($config['destination-folder'] != $oldConfig['destination-folder']) || ($config['destination-extension'] != $oldConfig['destination-extension'])) {
198
+ $whatShouldIt = '';
199
+ if ($config['destination-folder'] == $oldConfig['destination-folder']) {
200
+ $whatShouldIt = 'renamed';
201
+ $whatShouldIt2 = 'rename';
202
+ } else {
203
+ if ($config['destination-extension'] == $oldConfig['destination-extension']) {
204
+ $whatShouldIt = 'relocated';
205
+ $whatShouldIt2 = 'relocate';
206
+ } else {
207
+ $whatShouldIt = 'relocated and renamed';
208
+ $whatShouldIt2 = 'relocate and rename';
209
+ }
210
+ }
211
+
212
+ list($numFilesMoved, $numFilesFailedMoving) = CacheMover::move($config, $oldConfig);
213
+ if ($numFilesFailedMoving == 0) {
214
+ if ($numFilesMoved == 0) {
215
+ Messenger::addMessage(
216
+ 'notice',
217
+ 'No cached webp files needed to be ' . $whatShouldIt
218
+ );
219
+
220
+ } else {
221
+ Messenger::addMessage(
222
+ 'success',
223
+ 'The webp files was ' . $whatShouldIt . ' (' . $whatShouldIt . ' ' . $numFilesMoved . ' images)'
224
+ );
225
+ }
226
+ } else {
227
+ if ($numFilesMoved == 0) {
228
+ Messenger::addMessage(
229
+ 'warning',
230
+ 'No webp files could not be ' . $whatShouldIt . ' (failed to ' . $whatShouldIt2 . ' ' . $numFilesFailedMoving . ' images)'
231
+ );
232
+ } else {
233
+ Messenger::addMessage(
234
+ 'warning',
235
+ 'Some webp files could not be ' . $whatShouldIt . ' (failed to ' . $whatShouldIt2 . ' ' . $numFilesFailedMoving . ' images, but successfully ' . $whatShouldIt . ' ' . $numFilesMoved . ' images)'
236
+ );
237
+
238
+ }
239
+ }
240
+ }
241
+
242
+
243
  if (!$result['rules-needed-update']) {
244
  Messenger::addMessage(
245
  'success',
test/test.jpg.webp ADDED
Binary file
test/test.webp ADDED
Binary file
webp-express.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: WebP Express
4
  * Plugin URI: https://github.com/rosell-dk/webp-express
5
  * Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
6
- * Version: 0.9.1
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
3
  * Plugin Name: WebP Express
4
  * Plugin URI: https://github.com/rosell-dk/webp-express
5
  * Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
6
+ * Version: 0.10.0
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
wod/.webp ADDED
Binary file
wod/webp-on-demand.php CHANGED
@@ -8,12 +8,12 @@ error_reporting(E_ALL);
8
  //exit;
9
 
10
  //require 'webp-on-demand-1.inc';
11
- require '../vendor/rosell-dk/webp-convert/build/webp-on-demand-1.inc';
12
  //require '../vendor/autoload.php';
13
 
14
  //print_r($_GET); exit;
15
 
16
  use \WebPConvert\WebPConvert;
 
17
 
18
  function loadConfig($configFilename) {
19
  if (!file_exists($configFilename)) {
@@ -96,29 +96,53 @@ if (!file_exists($source)) {
96
  //echo $source; exit;
97
 
98
 
99
- // Calculate destination
100
- $imageRoot = $webExpressContentDirAbs . '/webp-images';
101
 
102
- // Check if source is residing inside document root.
103
- // (it is, if path starts with document root + '/')
104
- if (substr($source, 0, strlen($docRoot) + 1) === $docRoot . '/') {
105
-
106
- // We store relative to document root.
107
- // "Eat" the left part off the source parameter which contains the document root.
108
- // and also eat the slash (+1)
109
- $sourceRel = substr($source, strlen($docRoot) + 1);
110
- $destination = $imageRoot . '/doc-root/' . $sourceRel . '.webp';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  } else {
112
- // Source file is residing outside document root.
113
- // we must add complete path to structure
114
- $destination = $imageRoot . '/abs' . $source . '.webp';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
116
 
117
- // If we wanted webp images to be located in same folder, with ie ".jpg.webp" extension:
118
- // $destination = $source . '.webp';
119
 
120
- // If we wanted webp images to be located in same folder, with ".webp" extension:
121
- // $destination = preg_replace('/\.(jpg|jpeg|png)$/', '.webp', $source);
122
 
123
  //echo $destination; exit;
124
 
@@ -126,9 +150,6 @@ if (substr($source, 0, strlen($docRoot) + 1) === $docRoot . '/') {
126
  //echo '<pre>' . print_r($options, true) . '</pre>';
127
  //exit;
128
 
129
- $options['require-for-conversion'] = 'webp-on-demand-2.inc';
130
- //$options['require-for-conversion'] = '../../../autoload.php';
131
-
132
  foreach ($options['converters'] as &$converter) {
133
  if (isset($converter['converter'])) {
134
  $converterId = $converter['converter'];
@@ -149,5 +170,48 @@ if ($options['forward-query-string']) {
149
  }
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  //echo "<pre>source: $source \ndestination: $destination \n\noptions:" . print_r($options, true) . '</pre>'; exit;
153
- WebPConvert::convertAndServe($source, $destination, $options);
8
  //exit;
9
 
10
  //require 'webp-on-demand-1.inc';
 
11
  //require '../vendor/autoload.php';
12
 
13
  //print_r($_GET); exit;
14
 
15
  use \WebPConvert\WebPConvert;
16
+ use \WebPConvert\ServeExistingOrHandOver;
17
 
18
  function loadConfig($configFilename) {
19
  if (!file_exists($configFilename)) {
96
  //echo $source; exit;
97
 
98
 
 
 
99
 
100
+ // Calculate $destination
101
+ // ----------------------
102
+ $mingled = (isset($options['destination-folder']) && ($options['destination-folder'] == 'mingled'));
103
+ $storeMingled = false;
104
+ if ($mingled) {
105
+ // Test if source folder is writable.
106
+ // We will only store "mingled", if it is.
107
+ $sourceFolder = preg_replace('/\\/[^\\/]*$/', '', $source);
108
+ if (@is_writable($sourceFolder) && @is_executable($sourceFolder)) {
109
+ $storeMingled = true;
110
+ } else {
111
+ header('X-WebP-Express-Notice: Cannot save file in same directory as source, falling back to separate folder', true);
112
+ if (isset($_GET['debug'])) {
113
+ echo 'Notice: Cannot save file in same directory as source, falling back to separate folder<br><br>';
114
+ }
115
+ }
116
+ }
117
+ if ($storeMingled) {
118
+ if (isset($options['destination-extension']) && ($options['destination-extension'] == 'append')) {
119
+ $destination = $source . '.webp';
120
+ } else {
121
+ $destination = preg_replace('/\\.(jpe?g|png)$/', '', $source) . '.webp';
122
+ }
123
  } else {
124
+
125
+ $imageRoot = $webExpressContentDirAbs . '/webp-images';
126
+
127
+ // Check if source is residing inside document root.
128
+ // (it is, if path starts with document root + '/')
129
+ if (substr($source, 0, strlen($docRoot) + 1) === $docRoot . '/') {
130
+
131
+ // We store relative to document root.
132
+ // "Eat" the left part off the source parameter which contains the document root.
133
+ // and also eat the slash (+1)
134
+ $sourceRel = substr($source, strlen($docRoot) + 1);
135
+ $destination = $imageRoot . '/doc-root/' . $sourceRel . '.webp';
136
+ } else {
137
+ // Source file is residing outside document root.
138
+ // we must add complete path to structure
139
+ $destination = $imageRoot . '/abs' . $source . '.webp';
140
+ }
141
  }
142
 
 
 
143
 
144
+
145
+
146
 
147
  //echo $destination; exit;
148
 
150
  //echo '<pre>' . print_r($options, true) . '</pre>';
151
  //exit;
152
 
 
 
 
153
  foreach ($options['converters'] as &$converter) {
154
  if (isset($converter['converter'])) {
155
  $converterId = $converter['converter'];
170
  }
171
  }
172
 
173
+ function aboutToServeImageCallBack($servingWhat, $whyServingThis, $obj) {
174
+ return false; // do not serve!
175
+ }
176
+
177
+ $options['require-for-conversion'] = 'webp-on-demand-2.inc';
178
+ //$options['require-for-conversion'] = '../../../autoload.php';
179
+
180
+ include_once '../vendor/rosell-dk/webp-convert/build/webp-on-demand-1.inc';
181
+
182
+ if (isset($options['success-response']) && ($options['success-response'] == 'original')) {
183
+
184
+ /*
185
+ We want to convert, but serve the original. This is a bit unusual and requires a little tweaking
186
+
187
+ First, we use the "decideWhatToServe" method of WebPConvert to find out if we should convert or not
188
+
189
+ If result is "destination", it means there is a useful webp image at the destination (no reason to convert)
190
+ If result is "source", it means that source is lighter than existing webp image (no reason to convert)
191
+ If result is "fresh-conversion", it means we should convert
192
+ */
193
+ $server = new \WebPConvert\Serve\ServeExistingOrHandOver($source, $destination, $options);
194
+ $server->decideWhatToServe();
195
+
196
+ if ($server->whatToServe == 'fresh-conversion') {
197
+ // Conversion time.
198
+ // To prevent the serving, we use the callback
199
+ $options['aboutToServeImageCallBack'] = 'aboutToServeImageCallBack';
200
+ WebPConvert::convertAndServe($source, $destination, $options);
201
+
202
+ // remove the callback, we are going for another round
203
+ unset($options['aboutToServeImageCallBack']);
204
+ unset($options['require-for-conversion']);
205
+ }
206
+
207
+ // Serve time
208
+ $options['serve-original'] = true; // Serve original
209
+ $options['add-vary-header'] = false;
210
+
211
+ WebPConvert::convertAndServe($source, $destination, $options);
212
+
213
+ } else {
214
+ WebPConvert::convertAndServe($source, $destination, $options);
215
+ }
216
+
217
  //echo "<pre>source: $source \ndestination: $destination \n\noptions:" . print_r($options, true) . '</pre>'; exit;