WebP Express - Version 0.9.0

Version Description

  • (released 27 dec 2018) *
  • Optionally make .htaccess redirect directly to existing webp (improves performance)
  • 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)
  • Fixed some bugs

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

Download this release

Release Info

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

Code changes from version 0.8.1 to 0.9.0

Files changed (35) hide show
  1. README.md +38 -10
  2. README.txt +34 -19
  3. changelog.txt +8 -0
  4. lib/classes/Config.php +178 -0
  5. lib/classes/HTAccess.php +158 -40
  6. lib/classes/Paths.php +30 -8
  7. lib/classes/TestRun.php +25 -6
  8. lib/migrate/migrate1.php +1 -1
  9. lib/options/css/webp-express-options-page.css +31 -0
  10. lib/options/enqueue_scripts.php +27 -12
  11. lib/options/js/converters.js +16 -4
  12. lib/options/js/page.js +11 -0
  13. lib/options/js/whitelist.js +4 -0
  14. lib/options/options-hooks.php +13 -7
  15. lib/options/options/cache-control.inc +25 -0
  16. lib/options/{converter-options → options/converter-options}/cwebp.php +0 -0
  17. lib/options/{converter-options → options/converter-options}/ewww.php +0 -0
  18. lib/options/{converter-options → options/converter-options}/gd.php +0 -0
  19. lib/options/{converter-options → options/converter-options}/imagick.php +0 -0
  20. lib/options/{converter-options → options/converter-options}/imagickbinary.php +0 -0
  21. lib/options/{converter-options → options/converter-options}/wpc.php +0 -0
  22. lib/options/options/converters.inc +76 -0
  23. lib/options/options/do-not-pass-source-path-in-query-string.inc +6 -0
  24. lib/options/options/image-types.inc +22 -0
  25. lib/options/options/metadata.inc +16 -0
  26. lib/options/options/quality.inc +52 -0
  27. lib/options/options/redirect-to-existing.inc +8 -0
  28. lib/options/options/response-on-failure.inc +17 -0
  29. lib/options/options/web-service.inc +58 -0
  30. lib/options/page.php +70 -534
  31. lib/options/submit.php +3 -1
  32. web-service/.htaccess +9 -0
  33. webp-express.php +1 -1
  34. wod/.htaccess +9 -0
  35. wod/webp-on-demand.php +104 -30
README.md CHANGED
@@ -117,36 +117,64 @@ It is possible to make WebP Express work on NGINX, but it requieres manually ins
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
  ```
121
  if ($http_accept ~* "webp"){
122
  rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
123
  }
124
  ```
 
 
 
 
 
 
 
 
 
125
 
126
  The `wp-content` argument must point to the wp-content folder (relative to document root). In most installations, it is 'wp-content'.
127
 
128
- Discussion on this topic here: https://wordpress.org/support/topic/nginx-rewrite-rules-4/
129
 
130
  ### I am on a WAMP stack
131
  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?
132
 
133
  ### Why do I not see the option to set WebP quality to auto?
134
- The option will only display, if your system is able to detect jpeg qualities. To make your server capable to do that, install *Imagick* or *Gmagick*
 
 
 
 
135
 
136
  ### How do I make this work with a CDN?
137
  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.
138
 
139
  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.
140
 
141
- ## Changes in 0.8.0
142
- - New conversion method, which calls imagick binary directly. This will make WebP express work out of the box on more systems
143
- - Made sure not to trigger LFI warning i Wordfence (to activate, click the force .htaccess button)
144
- - Imagick can now be configured to set quality to auto (but only when the "auto" option isn't generally available)
145
- - Added Last-Modified header to images. This makes image caching work better
146
- - On some systems, converted files where stored in ie *..doc-rootwp-content..* rather than *..doc-root/wp-content..*. This is fixed, a clean-up script corrects the file structure upon upgrade.
147
- - Added condition in .htaccess that checks that source file exists before handing over to converter
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- For more info, see the closed issues on the 0.8.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.8.0
150
 
151
  ## Supporting WebP Express
152
  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:
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
+
146
+ If you have the *Imagick*, the *Imagick binary* or the *Remote WebP Express* conversion method working, but don't have the global "auto" option, you will have the auto option available in options of the individual converter.
147
+
148
+ 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/).
149
 
150
  ### How do I make this work with a CDN?
151
  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.
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
+
158
+ 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.
159
+
160
+ The following lazy load plugins/frameworks has been tested and works with *WebP Express*:
161
+ - [BJ Lazy Load](https://da.wordpress.org/plugins/bj-lazy-load/)
162
+ - [Owl Carousel 2](https://owlcarousel2.github.io/OwlCarousel2/)
163
+
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
+
171
+
172
+ ## Changes in 0.9.0
173
+ - Optionally make .htaccess redirect directly to existing webp (improves performance)
174
+ - 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)
175
+ - Fixed some bugs
176
 
177
+ 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
178
 
179
  ## Supporting WebP Express
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:
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.8.0
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -124,29 +124,19 @@ 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
 
@@ -167,6 +157,21 @@ The plugin takes care of setting the "Vary" HTTP header to "Accept" when routing
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
 
@@ -179,13 +184,12 @@ The following lazy load plugins/frameworks has been tested and works with *WebP
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)
189
 
190
  == Screenshots ==
191
 
@@ -193,6 +197,14 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell)
193
 
194
  == Changelog ==
195
 
 
 
 
 
 
 
 
 
196
  = 0.8.1 =
197
  * Fixed javascript bug
198
 
@@ -261,5 +273,8 @@ For older releases, check out changelog.txt
261
 
262
  == Upgrade Notice ==
263
 
 
 
 
264
  = 0.8.0 =
265
  New converter and miscellaneous improvements
4
  Tags: webp, images, performance
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
+ Stable tag: 0.9.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`). For standard wordpress installations, 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?xsource=x$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
+ 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`).
139
+
140
  Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
141
 
142
 
157
  https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values#Values_for_an_image
158
  - so it is not an issue.
159
 
160
+ 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)
161
+
162
+ = I am on Cloudflare =
163
+ 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.
164
+
165
+ 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.
166
+
167
+ To make *WebP Express* work on a free Cloudflare account, you have the following choices:
168
+
169
+ 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*
170
+
171
+ 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.
172
+
173
+ 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)
174
+
175
  = Does it work with lazy loaded images? =
176
  No plugins/frameworks has yet been discovered, which does not work with *WebP Express*.
177
 
184
  = When is feature X coming? =
185
  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.
186
 
187
+ Here are my loose plans ahead: The 0.10 release adds possibility to configure where the generated webp files are located. This together with a few other new options allows WebP Express to work together with the [Cache Enabler](https://wordpress.org/plugins/cache-enabler/) plugin. This is a great combination, which eliminates the need to redirect jpeg to webp. Read more about it [here](https://github.com/rosell-dk/webp-express/issues/133). 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. 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
188
 
189
  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.
190
 
 
191
  = How do I buy you a cup of coffee? =
192
+ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacoff.ee/rosell).
193
 
194
  == Screenshots ==
195
 
197
 
198
  == Changelog ==
199
 
200
+ = 0.9.0 =
201
+ * (released 27 dec 2018) *
202
+ * Optionally make .htaccess redirect directly to existing webp (improves performance)
203
+ * 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)
204
+ * Fixed some bugs
205
+
206
+ 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
207
+
208
  = 0.8.1 =
209
  * Fixed javascript bug
210
 
273
 
274
  == Upgrade Notice ==
275
 
276
+ = 0.9.0 =
277
+ Option to redirect to existing webp images directly in .htaccess (improves performance)
278
+
279
  = 0.8.0 =
280
  New converter and miscellaneous improvements
changelog.txt CHANGED
@@ -1,3 +1,11 @@
 
 
 
 
 
 
 
 
1
  = 0.8.1 =
2
  * (released 11 dec 2018) *
3
  * Fixed javascript bug
1
+ = 0.9.0 =
2
+ * (released 27 dec 2018) *
3
+ * Optionally make .htaccess redirect directly to existing webp (improves performance)
4
+ * 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)
5
+ * Fixed some bugs
6
+
7
+ 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
8
+
9
  = 0.8.1 =
10
  * (released 11 dec 2018) *
11
  * Fixed javascript bug
lib/classes/Config.php CHANGED
@@ -2,6 +2,9 @@
2
 
3
  namespace WebPExpress;
4
 
 
 
 
5
  include_once "FileHelper.php";
6
  use \WebPExpress\FileHelper;
7
 
@@ -17,6 +20,9 @@ use \WebPExpress\Paths;
17
  include_once "State.php";
18
  use \WebPExpress\State;
19
 
 
 
 
20
  class Config
21
  {
22
 
@@ -52,6 +58,174 @@ class Config
52
  return self::loadJSONOptions(Paths::getConfigFileName());
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  public static function isConfigFileThere()
56
  {
57
  return (FileHelper::fileExists(Paths::getConfigFileName()));
@@ -106,6 +280,7 @@ class Config
106
  unset ($c['id']);
107
  unset($c['working']);
108
  unset($c['error']);
 
109
  if (isset($c['options']['quality']) && ($c['options']['quality'] == 'inherit')) {
110
  unset ($c['options']['quality']);
111
  }
@@ -148,6 +323,9 @@ class Config
148
  unset($options['image-types']);
149
  unset($options['cache-control']);
150
  unset($options['cache-control-custom']);
 
 
 
151
 
152
  return $options;
153
  }
2
 
3
  namespace WebPExpress;
4
 
5
+ include_once "ConvertersHelper.php";
6
+ use \WebPExpress\ConvertersHelper;
7
+
8
  include_once "FileHelper.php";
9
  use \WebPExpress\FileHelper;
10
 
20
  include_once "State.php";
21
  use \WebPExpress\State;
22
 
23
+ include_once "TestRun.php";
24
+ use \WebPExpress\TestRun;
25
+
26
  class Config
27
  {
28
 
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' => [
92
+ /*[
93
+ 'uid' => '', // for internal purposes
94
+ 'label' => '', // ie website name. It is just for display
95
+ 'ip' => '', // restrict to these ips. * pattern is allowed.
96
+ 'api-key' => '', // Api key for the entry. Not neccessarily unique for the entry
97
+ //'quota' => 60
98
+ ]
99
+ */
100
+ ]
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
+ }
121
+ if (!isset($config['web-service']['whitelist'])) {
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
147
+ // Send ewww converter to top
148
+ $resultPart1 = [];
149
+ $resultPart2 = [];
150
+ foreach ($defaultConverters as $converter) {
151
+ $converterId = $converter['converter'];
152
+ if ($converterId == 'ewww') {
153
+ $resultPart1[] = $converter;
154
+ } else {
155
+ $resultPart2[] = $converter;
156
+ }
157
+ }
158
+ $config['converters'] = array_merge($resultPart1, $resultPart2);
159
+ } else {
160
+ // Send converters not working to the bottom
161
+ // - and also deactivate them..
162
+ $resultPart1 = [];
163
+ $resultPart2 = [];
164
+ foreach ($defaultConverters as $converter) {
165
+ $converterId = $converter['converter'];
166
+ if (in_array($converterId, $workingConverters)) {
167
+ $resultPart1[] = $converter;
168
+ } else {
169
+ $converter['deactivated'] = true;
170
+ $resultPart2[] = $converter;
171
+ }
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) {
187
+ foreach ($config['converters'] as &$converter) {
188
+ $converterId = $converter['converter'];
189
+ $hasError = isset($testResult['errors'][$converterId]);
190
+ $working = !$hasError;
191
+ if (isset($converter['working']) && ($converter['working'] != $working)) {
192
+ if ($working) {
193
+ Messenger::printMessage(
194
+ 'info',
195
+ 'Hurray! - The <i>' . webpexpress_converterName($converterId) . '</i> conversion method is working now!'
196
+ );
197
+ } else {
198
+ Messenger::printMessage(
199
+ 'warning',
200
+ 'Sad news. The <i>' . webpexpress_converterName($converterId) . '</i> conversion method is not working anymore. What happened?'
201
+ );
202
+ }
203
+ }
204
+ $converter['working'] = $working;
205
+ if ($hasError) {
206
+ $error = $testResult['errors'][$converterId];
207
+ if ($converterId == 'wpc') {
208
+ if (preg_match('/Missing URL/', $error)) {
209
+ $error = 'Not configured';
210
+ }
211
+ if ($error == 'No remote host has been set up') {
212
+ $error = 'Not configured';
213
+ }
214
+
215
+ if (preg_match('/cloud service is not enabled/', $error)) {
216
+ $error = 'The server is not enabled. Click the "Enable web service" on WebP Express settings on the site you are trying to connect to.';
217
+ }
218
+ }
219
+ $converter['error'] = $error;
220
+ } else {
221
+ unset($converter['error']);
222
+ }
223
+ }
224
+ }
225
+ self::$configForOptionsPage = $config; // cache the result
226
+ return $config;
227
+ }
228
+
229
  public static function isConfigFileThere()
230
  {
231
  return (FileHelper::fileExists(Paths::getConfigFileName()));
280
  unset ($c['id']);
281
  unset($c['working']);
282
  unset($c['error']);
283
+
284
  if (isset($c['options']['quality']) && ($c['options']['quality'] == 'inherit')) {
285
  unset ($c['options']['quality']);
286
  }
323
  unset($options['image-types']);
324
  unset($options['cache-control']);
325
  unset($options['cache-control-custom']);
326
+ //unset($options['forward-query-string']); // It is used in webp-on-demand.php, so do not unset!
327
+ unset($options['do-not-pass-source-in-query-string']);
328
+ unset($options['redirect-to-existing-in-htaccess']);
329
 
330
  return $options;
331
  }
lib/classes/HTAccess.php CHANGED
@@ -16,10 +16,9 @@ use \WebPExpress\State;
16
 
17
  class HTAccess
18
  {
19
-
20
- public static function generateHTAccessRulesFromConfigObj($config)
21
  {
22
-
23
  /* Calculate $fileExt */
24
  $imageTypes = $config['image-types'];
25
  $fileExtensions = [];
@@ -34,6 +33,7 @@ class HTAccess
34
  if ($imageTypes == 0) {
35
  return '# WebP Express disabled (no image types have been choosen to be converted)';
36
  }
 
37
  /* Build rules */
38
  $rules = '';
39
 
@@ -45,41 +45,126 @@ class HTAccess
45
  $rules .= "<IfModule mod_rewrite.c>\n" .
46
  " RewriteEngine On\n\n";
47
 
48
- $pathToExisting = Paths::getPathToExisting();
49
-
50
- /*
51
- // TODO: handle when wp-content is outside document root.
52
- // TODO: this should be made optional
53
- if (true) {
54
- # Redirect to existing converted image (under appropriate circumstances)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  $rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
 
56
  $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $pathToExisting . "/$1.$2.webp -f\n";
57
- $rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $pathToExisting . "/$1.$2.webp [NC,T=image/webp,QSD,L]\n\n";
58
- }*/
59
-
60
 
 
 
 
 
 
 
 
 
 
61
  $rules .= " # Redirect images to webp-on-demand.php (if browser supports webp)\n";
62
  $rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
63
  $rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
64
  if ($config['forward-query-string']) {
65
  $rules .= " RewriteCond %{QUERY_STRING} (.*)\n";
66
  }
 
 
 
 
 
67
  $rules .= " RewriteRule ^(.*)\.(" . $fileExt . ")$ " .
68
  "/" . Paths::getWodUrlPath() .
69
- "?xsource=x%{SCRIPT_FILENAME}" .
70
- "&wp-content=" . Paths::getWPContentDirRel() .
71
  ($config['forward-query-string'] ? '&%1' : '') .
72
- " [NC,L]\n";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- $rules .="</IfModule>\n" .
75
- "AddType image/webp .webp\n";
76
 
77
  return $rules;
78
  }
79
 
80
- public static function generateHTAccessRulesFromConfigFile() {
 
81
  if (Config::isConfigFileThereAndOk()) {
82
- return self::generateHTAccessRulesFromConfigObj(Config::loadConfig());
83
  } else {
84
  return false;
85
  }
@@ -122,10 +207,42 @@ class HTAccess
122
  return true;
123
  }
124
 
125
- $propsToCompare = ['forward-query-string', 'image-types'];
 
 
 
 
 
126
 
127
 
128
  foreach ($propsToCompare as $prop) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  if ($newConfig[$prop] != $oldConfig[$prop]) {
130
  return true;
131
  }
@@ -285,6 +402,7 @@ class HTAccess
285
  return $success;
286
  }
287
 
 
288
  public static function saveHTAccessRulesToFirstWritableHTAccessDir($dirs, $rules)
289
  {
290
  foreach ($dirs as $dir) {
@@ -372,35 +490,33 @@ class HTAccess
372
  /**
373
  * Try to save the rules.
374
  * Returns many details
 
375
  */
376
  public static function saveRules($config) {
377
 
378
- $rules = HTAccess::generateHTAccessRulesFromConfigObj($config);
379
 
380
  list($minRequired, $pluginToo, $uploadToo) = self::getHTAccessDirRequirements();
381
 
382
- $indexDir = Paths::getIndexDirAbs();
383
  $wpContentDir = Paths::getWPContentDirAbs();
384
-
385
- $acceptableDirs = [
386
- $wpContentDir
387
- ];
388
- if ($minRequired == 'index') {
389
- $acceptableDirs[] = $indexDir;
390
- }
391
 
392
  $overidingRulesInWpContentWarning = false;
393
- $result = HTAccess::saveHTAccessRulesToFirstWritableHTAccessDir($acceptableDirs, $rules);
394
- if ($result == $wpContentDir) {
 
 
 
 
 
 
 
 
 
 
 
395
  $mainResult = 'wp-content';
396
- //if (self::haveWeRulesInThisHTAccessBestGuess($indexDir . '/.htaccess')) {
397
- HTAccess::saveHTAccessRulesToFile($indexDir . '/.htaccess', '# WebP Express has placed its rules in your wp-content dir. Go there.', false);
398
- //}
399
- } elseif ($result == $indexDir) {
400
- $mainResult = 'index';
401
- $overidingRulesInWpContentWarning = self::haveWeRulesInThisHTAccessBestGuess($wpContentDir . '/.htaccess');
402
- } elseif ($result === false) {
403
- $mainResult = 'failed';
404
  }
405
 
406
  /* plugin */
@@ -416,6 +532,7 @@ class HTAccess
416
  $pluginFailed = false;
417
  $pluginFailedBadly = true;
418
  if ($pluginToo == 'yes') {
 
419
  $pluginDir = Paths::getPluginDirAbs();
420
  $pluginFailed = !(HTAccess::saveHTAccessRulesToFile($pluginDir . '/.htaccess', $rules, true));
421
  if ($pluginFailed) {
@@ -437,6 +554,7 @@ class HTAccess
437
  $uploadFailedBadly = true;
438
  if ($uploadToo == 'yes') {
439
  $uploadDir = Paths::getUploadDirAbs();
 
440
  $uploadFailed = !(HTAccess::saveHTAccessRulesToFile($uploadDir . '/.htaccess', $rules, true));
441
  if ($uploadFailed) {
442
  $uploadFailedBadly = self::haveWeRulesInThisHTAccessBestGuess($uploadDir . '/.htaccess');
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 = [];
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
 
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
+
78
+ // TODO: It seems $pathToExisting needs to be adjusted, depending on where the .htaccess is located
79
+ // Ie, if plugin folder has been moved out of ABSPATH, we should ie set
80
+ // $pathToExisting to 'doc-root/plugins-moved/'
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" .
146
+ " # or existing jpg" .
147
+
148
+ " # Apache appends \"REDIRECT_\" in front of the environment variables, but LiteSpeed does not.\n" .
149
+ " # These next line is for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
150
+ " SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1\n\n" .
151
+
152
+ " # Make CDN caching possible.\n" .
153
+ " # The effect is that the CDN will cache both the webp image and the jpeg/png image and return the proper\n" .
154
+ " # image to the proper clients (for this to work, make sure to set up CDN to forward the \"Accept\" header)\n" .
155
+ " Header append Vary Accept env=WEBPACCEPT\n" .
156
+ "</IfModule>\n\n";
157
+ }*/
158
 
159
+ $rules .= "AddType image/webp .webp\n";
 
160
 
161
  return $rules;
162
  }
163
 
164
+ /* only called from page-messages.inc, but commented out there... */
165
+ public static function generateHTAccessRulesFromConfigFile($htaccessDir = '') {
166
  if (Config::isConfigFileThereAndOk()) {
167
+ return self::generateHTAccessRulesFromConfigObj(Config::loadConfig(), $htaccessDir);
168
  } else {
169
  return false;
170
  }
207
  return true;
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;
248
  }
402
  return $success;
403
  }
404
 
405
+ /* only called in this file */
406
  public static function saveHTAccessRulesToFirstWritableHTAccessDir($dirs, $rules)
407
  {
408
  foreach ($dirs as $dir) {
490
  /**
491
  * Try to save the rules.
492
  * Returns many details
493
+ * (called from migrate1.php, reactivate.php, Config.php and this file)
494
  */
495
  public static function saveRules($config) {
496
 
 
497
 
498
  list($minRequired, $pluginToo, $uploadToo) = self::getHTAccessDirRequirements();
499
 
500
+ $rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'wp-content');
501
  $wpContentDir = Paths::getWPContentDirAbs();
502
+ $wpContentFailed = !(HTAccess::saveHTAccessRulesToFile($wpContentDir . '/.htaccess', $rules, true));
 
 
 
 
 
 
503
 
504
  $overidingRulesInWpContentWarning = false;
505
+ if ($wpContentFailed) {
506
+ if ($minRequired == 'index') {
507
+ $rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'index');
508
+ $indexFailed = !(HTAccess::saveHTAccessRulesToFile(Paths::getIndexDirAbs() . '/.htaccess', $rules, true));
509
+
510
+ if ($indexFailed) {
511
+ $mainResult = 'failed';
512
+ } else {
513
+ $mainResult = 'index';
514
+ $overidingRulesInWpContentWarning = self::haveWeRulesInThisHTAccessBestGuess($wpContentDir . '/.htaccess');
515
+ }
516
+ }
517
+ } else {
518
  $mainResult = 'wp-content';
519
+ HTAccess::saveHTAccessRulesToFile(Paths::getIndexDirAbs() . '/.htaccess', '# WebP Express has placed its rules in your wp-content dir. Go there.', false);
 
 
 
 
 
 
 
520
  }
521
 
522
  /* plugin */
532
  $pluginFailed = false;
533
  $pluginFailedBadly = true;
534
  if ($pluginToo == 'yes') {
535
+ $rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'plugin');
536
  $pluginDir = Paths::getPluginDirAbs();
537
  $pluginFailed = !(HTAccess::saveHTAccessRulesToFile($pluginDir . '/.htaccess', $rules, true));
538
  if ($pluginFailed) {
554
  $uploadFailedBadly = true;
555
  if ($uploadToo == 'yes') {
556
  $uploadDir = Paths::getUploadDirAbs();
557
+ $rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'uploads');
558
  $uploadFailed = !(HTAccess::saveHTAccessRulesToFile($uploadDir . '/.htaccess', $rules, true));
559
  if ($uploadFailed) {
560
  $uploadFailedBadly = self::haveWeRulesInThisHTAccessBestGuess($uploadDir . '/.htaccess');
lib/classes/Paths.php CHANGED
@@ -20,14 +20,22 @@ class Paths
20
  }
21
 
22
  /**
23
- * Find out if $dir1 is inside - or equal to - $dir2
24
- */
25
  public static function isDirInsideDir($dir1, $dir2)
26
  {
27
  $rel = PathHelper::getRelDir($dir2, $dir1);
28
  return (substr($rel, 0, 3) != '../');
29
  }
30
 
 
 
 
 
 
 
 
 
31
  // ------------ Home Dir -------------
32
 
33
  public static function getHomeDirAbs()
@@ -40,7 +48,7 @@ class Paths
40
 
41
  public static function getHomeDirRel()
42
  {
43
- return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getHomeDirAbs());
44
  }
45
 
46
  // ------------ Index Dir -------------
@@ -51,6 +59,11 @@ class Paths
51
  return rtrim(ABSPATH, '/');
52
  }
53
 
 
 
 
 
 
54
 
55
  // ------------ .htaccess dir -------------
56
  // (directory containing the relevant .htaccess)
@@ -79,7 +92,7 @@ class Paths
79
  }
80
  public static function getWPContentDirRel()
81
  {
82
- return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getWPContentDirAbs());
83
  }
84
 
85
  public static function isWPContentDirMoved()
@@ -106,7 +119,7 @@ class Paths
106
 
107
  public static function getContentDirRel()
108
  {
109
- return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getContentDirAbs());
110
  }
111
 
112
  public static function createContentDirIfMissing()
@@ -120,6 +133,10 @@ class Paths
120
  $upload_dir = wp_upload_dir(null, false);
121
  return $upload_dir['basedir'];
122
  }
 
 
 
 
123
 
124
  /*
125
  public static function getUploadDirAbs()
@@ -150,7 +167,7 @@ class Paths
150
 
151
  public static function getConfigDirRel()
152
  {
153
- return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getConfigDirAbs());
154
  }
155
 
156
  public static function createConfigDirIfMissing()
@@ -196,7 +213,7 @@ APACHE
196
 
197
  public static function getCacheDirRel()
198
  {
199
- return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getCacheDirAbs());
200
  }
201
 
202
  public static function createCacheDirIfMissing()
@@ -211,6 +228,11 @@ APACHE
211
  return untrailingslashit(WP_PLUGIN_DIR);
212
  }
213
 
 
 
 
 
 
214
  public static function isPluginDirMovedOutOfAbsPath()
215
  {
216
  return !(self::isDirInsideDir(self::getPluginDirAbs(), ABSPATH));
@@ -305,7 +327,7 @@ APACHE
305
  */
306
  public static function getPathToExisting()
307
  {
308
- return self::getCacheDirRel() . '/' . self::getHomeDirRel();
309
  }
310
 
311
  public static function getUrlsAndPathsForTheJavascript()
20
  }
21
 
22
  /**
23
+ * Find out if $dir1 is inside - or equal to - $dir2
24
+ */
25
  public static function isDirInsideDir($dir1, $dir2)
26
  {
27
  $rel = PathHelper::getRelDir($dir2, $dir1);
28
  return (substr($rel, 0, 3) != '../');
29
  }
30
 
31
+ /**
32
+ * Return relative dir - relative to realpath(document root)
33
+ */
34
+ public static function getRelDir($dir)
35
+ {
36
+ return PathHelper::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir);
37
+ }
38
+
39
  // ------------ Home Dir -------------
40
 
41
  public static function getHomeDirAbs()
48
 
49
  public static function getHomeDirRel()
50
  {
51
+ return self::getRelDir(self::getHomeDirAbs());
52
  }
53
 
54
  // ------------ Index Dir -------------
59
  return rtrim(ABSPATH, '/');
60
  }
61
 
62
+ public static function getIndexDirRel()
63
+ {
64
+ return self::getRelDir(self::getIndexDirAbs());
65
+ }
66
+
67
 
68
  // ------------ .htaccess dir -------------
69
  // (directory containing the relevant .htaccess)
92
  }
93
  public static function getWPContentDirRel()
94
  {
95
+ return self::getRelDir(self::getWPContentDirAbs());
96
  }
97
 
98
  public static function isWPContentDirMoved()
119
 
120
  public static function getContentDirRel()
121
  {
122
+ return self::getRelDir(self::getContentDirAbs());
123
  }
124
 
125
  public static function createContentDirIfMissing()
133
  $upload_dir = wp_upload_dir(null, false);
134
  return $upload_dir['basedir'];
135
  }
136
+ public static function getUploadDirRel()
137
+ {
138
+ return self::getRelDir(self::getUploadDirAbs());
139
+ }
140
 
141
  /*
142
  public static function getUploadDirAbs()
167
 
168
  public static function getConfigDirRel()
169
  {
170
+ return self::getRelDir(self::getConfigDirAbs());
171
  }
172
 
173
  public static function createConfigDirIfMissing()
213
 
214
  public static function getCacheDirRel()
215
  {
216
+ return self::getRelDir(self::getCacheDirAbs());
217
  }
218
 
219
  public static function createCacheDirIfMissing()
228
  return untrailingslashit(WP_PLUGIN_DIR);
229
  }
230
 
231
+ public static function getPluginDirRel()
232
+ {
233
+ return self::getRelDir(self::getPluginDirAbs());
234
+ }
235
+
236
  public static function isPluginDirMovedOutOfAbsPath()
237
  {
238
  return !(self::isDirInsideDir(self::getPluginDirAbs(), ABSPATH));
327
  */
328
  public static function getPathToExisting()
329
  {
330
+ return self::getCacheDirRel() . '/doc-root/' . self::getHomeDirRel();
331
  }
332
 
333
  public static function getUrlsAndPathsForTheJavascript()
lib/classes/TestRun.php CHANGED
@@ -25,16 +25,24 @@ class TestRun
25
  {
26
 
27
 
 
 
28
  /**
29
  * Get an array of working converters OR false, if tests cannot be made
30
  */
31
  public static function getConverterStatus() {
 
 
 
 
 
32
  $source = Paths::getWebPExpressPluginDirAbs() . '/test/small-q61.jpg';
33
  $destination = Paths::getUploadDirAbs() . '/webp-express-test-conversion.webp';
34
  if (!FileHelper::canCreateFile($destination)) {
35
  $destination = Paths::getWPContentDirAbs() . '/webp-express-test-conversion.webp';
36
  }
37
- if (!FileHelper::canCreateFile($destination)) {
 
38
  return false;
39
  }
40
  $workingConverters = [];
@@ -79,16 +87,27 @@ class TestRun
79
  }
80
  }
81
  //print_r($errors);
82
- return [
 
 
83
  'workingConverters' => $workingConverters,
84
  'errors' => $errors
85
  ];
 
86
  }
87
 
 
 
 
88
  public static function isLocalQualityDetectionWorking() {
89
- $q = ConverterHelper::detectQualityOfJpg(
90
- Paths::getWebPExpressPluginDirAbs() . '/test/small-q61.jpg'
91
- );
92
- return ($q === 61);
 
 
 
 
 
93
  }
94
  }
25
  {
26
 
27
 
28
+ public static $converterStatus = null; // to cache the result
29
+
30
  /**
31
  * Get an array of working converters OR false, if tests cannot be made
32
  */
33
  public static function getConverterStatus() {
34
+
35
+ // Is result cached?
36
+ if (isset(self::$converterStatus)) {
37
+ return self::$converterStatus;
38
+ }
39
  $source = Paths::getWebPExpressPluginDirAbs() . '/test/small-q61.jpg';
40
  $destination = Paths::getUploadDirAbs() . '/webp-express-test-conversion.webp';
41
  if (!FileHelper::canCreateFile($destination)) {
42
  $destination = Paths::getWPContentDirAbs() . '/webp-express-test-conversion.webp';
43
  }
44
+ if (!FileHelper::canCreateFile($destination)) {
45
+ self::$converterStatus = false; // // cache the result
46
  return false;
47
  }
48
  $workingConverters = [];
87
  }
88
  }
89
  //print_r($errors);
90
+
91
+ // cache the result
92
+ self::$converterStatus = [
93
  'workingConverters' => $workingConverters,
94
  'errors' => $errors
95
  ];
96
+ return self::$converterStatus;
97
  }
98
 
99
+
100
+ public static $localQualityDetectionWorking = null; // to cache the result
101
+
102
  public static function isLocalQualityDetectionWorking() {
103
+ if (isset(self::$localQualityDetectionWorking)) {
104
+ return self::$localQualityDetectionWorking;
105
+ } else {
106
+ $q = ConverterHelper::detectQualityOfJpg(
107
+ Paths::getWebPExpressPluginDirAbs() . '/test/small-q61.jpg'
108
+ );
109
+ self::$localQualityDetectionWorking = ($q === 61);
110
+ return self::$localQualityDetectionWorking;
111
+ }
112
  }
113
  }
lib/migrate/migrate1.php CHANGED
@@ -88,7 +88,7 @@ function webpexpress_migrate1_migrateOptions()
88
  $config = $options;
89
 
90
  //$htaccessExists = Config::doesHTAccessExists();
91
- $rules = HTAccess::generateHTAccessRulesFromConfigObj($config);
92
 
93
  if (Config::saveConfigurationFile($config)) {
94
  $options = Config::generateWodOptionsFromConfigObj($config);
88
  $config = $options;
89
 
90
  //$htaccessExists = Config::doesHTAccessExists();
91
+ //$rules = HTAccess::generateHTAccessRulesFromConfigObj($config);
92
 
93
  if (Config::saveConfigurationFile($config)) {
94
  $options = Config::generateWodOptionsFromConfigObj($config);
lib/options/css/webp-express-options-page.css CHANGED
@@ -345,3 +345,34 @@
345
  display: inline-block;
346
  }
347
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  display: inline-block;
346
  }
347
  */
348
+
349
+ #webpexpress_settings .block {
350
+ position: relative;
351
+ margin: 0 auto 0.5rem;
352
+ padding: 0 1rem;
353
+ box-sizing: border-box;
354
+ background-color: #fff;
355
+ box-shadow: 0 0 0 1px rgba(200,215,225,0.25), 0 1px 2px #e9eff3;
356
+ }
357
+
358
+ #webpexpress_settings .block.buttons {
359
+ padding: 10px;
360
+ position: sticky;
361
+ top: 30px;
362
+ z-index: 1;
363
+ }
364
+
365
+ @media (min-width: 768px) {
366
+ #webpexpress_settings .block {
367
+ padding: 10px 1.5rem 20px;
368
+ }
369
+ }
370
+
371
+ #webpexpress_settings .block.buttons p.submit {
372
+ margin: 0;
373
+ padding: 0;
374
+ }
375
+
376
+ #webpexpress_settings .block.buttons table {
377
+ float: right;
378
+ }
lib/options/enqueue_scripts.php CHANGED
@@ -3,36 +3,51 @@
3
  include_once __DIR__ . '/../classes/Paths.php';
4
  use \WebPExpress\Paths;
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  wp_register_script('sortable', plugins_url('js/sortable.min.js', __FILE__), [], '1.9.0');
7
  wp_enqueue_script('sortable');
8
 
9
- wp_register_script('daspopup', plugins_url('js/das-popup.js', __FILE__), [], '0.7.0-dev5');
10
  wp_enqueue_script('daspopup');
11
 
12
- wp_register_script('converters', plugins_url('js/converters.js', __FILE__), ['sortable','daspopup'], '0.8.0-dev7');
 
 
 
 
 
13
  wp_enqueue_script('converters');
14
 
15
- wp_register_script('whitelist', plugins_url('js/whitelist.js', __FILE__), ['daspopup'], '0.7.0-dev15');
 
 
 
16
  wp_enqueue_script('whitelist');
17
 
18
  //wp_register_script('api_keys', plugins_url('js/api-keys.js', __FILE__), ['daspopup'], '0.7.0-dev8');
19
  //wp_enqueue_script('api_keys');
20
 
21
- wp_register_script( 'page', plugins_url('js/page.js', __FILE__), [], '0.7.0-dev5');
22
  wp_enqueue_script('page');
23
 
24
- if (function_exists('wp_add_inline_script')) {
25
- // wp_add_inline_script is available from Wordpress 4.5
26
- wp_add_inline_script('converters', 'window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';');
27
- } else {
28
- echo '<script>window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';</script>';
29
- }
30
 
31
  // Register styles
32
- wp_register_style('webp-express-options-page-css', plugins_url('css/webp-express-options-page.css', __FILE__), null, '0.8.0-dev4');
33
  wp_enqueue_style('webp-express-options-page-css');
34
 
35
- wp_register_style('das-popup-css', plugins_url('css/das-popup.css', __FILE__), null, '0.7.0-dev5');
36
  wp_enqueue_style('das-popup-css');
37
 
38
  add_thickbox();
3
  include_once __DIR__ . '/../classes/Paths.php';
4
  use \WebPExpress\Paths;
5
 
6
+ include_once __DIR__ . '/../classes/Config.php';
7
+ use \WebPExpress\Config;
8
+
9
+ $version = '0.9.0';
10
+
11
+ function webp_express_add_inline_script($id, $script, $position) {
12
+ if (function_exists('wp_add_inline_script')) {
13
+ // wp_add_inline_script is available from Wordpress 4.5
14
+ wp_add_inline_script($id, $script, $position);
15
+ } else {
16
+ echo '<script>' . $script . '</script>';
17
+ }
18
+ }
19
  wp_register_script('sortable', plugins_url('js/sortable.min.js', __FILE__), [], '1.9.0');
20
  wp_enqueue_script('sortable');
21
 
22
+ wp_register_script('daspopup', plugins_url('js/das-popup.js', __FILE__), [], $version);
23
  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');
41
 
42
+ wp_register_script( 'page', plugins_url('js/page.js', __FILE__), [], $version);
43
  wp_enqueue_script('page');
44
 
 
 
 
 
 
 
45
 
46
  // Register styles
47
+ wp_register_style('webp-express-options-page-css', plugins_url('css/webp-express-options-page.css', __FILE__), null, $version);
48
  wp_enqueue_style('webp-express-options-page-css');
49
 
50
+ wp_register_style('das-popup-css', plugins_url('css/das-popup.css', __FILE__), null, $version);
51
  wp_enqueue_style('das-popup-css');
52
 
53
  add_thickbox();
lib/options/js/converters.js CHANGED
@@ -13,6 +13,7 @@ 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
  for (var i=0; i<window.defaultConverters.length; i++) {
17
  var checkMe = window.defaultConverters[i];
18
  var found = false;
@@ -42,9 +43,10 @@ function addMissingConvertersAndOptions() {
42
  if (!found) {
43
  window.converters.push(window.defaultConverters[i]);
44
  }
45
- }
46
- console.log(window.converters);
47
- console.log(window.defaultConverters);
 
48
  }
49
 
50
  function getConversionMethodDescription(converterId) {
@@ -118,8 +120,12 @@ function generateConverterHTML(converter) {
118
 
119
  /* Set ids on global converters object */
120
  function setTemporaryIdsOnConverters() {
 
 
 
 
121
  var numConverterInstances = [];
122
- for (var i=0; i<converters.length; i++) {
123
  var converter = converters[i]['converter'];
124
  if (numConverterInstances[converter]) {
125
  numConverterInstances[converter]++;
@@ -162,10 +168,16 @@ function updateInputValue() {
162
 
163
  function setConvertersHTML() {
164
 
 
165
  var html = '';
166
 
167
  setTemporaryIdsOnConverters();
168
 
 
 
 
 
 
169
  for (var i=0; i<window.converters.length; i++) {
170
  var converter = converters[i];
171
  html += generateConverterHTML(converter);
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;
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) {
120
 
121
  /* Set ids on global converters object */
122
  function setTemporaryIdsOnConverters() {
123
+ if (window.converters == undefined) {
124
+ alert('window.converters is undefined. Strange. Please report!');
125
+ return;
126
+ }
127
  var numConverterInstances = [];
128
+ for (var i=0; i<window.converters.length; i++) {
129
  var converter = converters[i]['converter'];
130
  if (numConverterInstances[converter]) {
131
  numConverterInstances[converter]++;
168
 
169
  function setConvertersHTML() {
170
 
171
+
172
  var html = '';
173
 
174
  setTemporaryIdsOnConverters();
175
 
176
+ if (document.getElementById('converters') == null) {
177
+ alert('document.getElementById("converters") returns null. Strange! Please report.');
178
+ return;
179
+ }
180
+
181
  for (var i=0; i<window.converters.length; i++) {
182
  var converter = converters[i];
183
  html += generateConverterHTML(converter);
lib/options/js/page.js CHANGED
@@ -1,5 +1,16 @@
1
 
2
  function updateCacheControlCustomVisibility() {
 
 
 
 
 
 
 
 
 
 
 
3
  var cacheControlValue = document.getElementById('cache_control_select').value;
4
  var customEl = document.getElementById('cache_control_custom');
5
  if (cacheControlValue == 'custom') {
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') {
lib/options/js/whitelist.js CHANGED
@@ -1,4 +1,8 @@
1
  function updateWhitelistInputValue() {
 
 
 
 
2
  document.getElementById('whitelist').value = JSON.stringify(window.whitelist);
3
  }
4
 
1
  function updateWhitelistInputValue() {
2
+ if (document.getElementById('whitelist') == null) {
3
+ alert('document.getElementById("whitelist") returns null. Strange! Please report.');
4
+ return;
5
+ }
6
  document.getElementById('whitelist').value = JSON.stringify(window.whitelist);
7
  }
8
 
lib/options/options-hooks.php CHANGED
@@ -16,14 +16,20 @@ add_action('admin_post_webpexpress_settings_submit', function() {
16
  include __DIR__ . '/submit.php';
17
  });
18
 
19
- global $pagenow;
20
- if (($pagenow == 'options-general.php') && (isset($_GET['page'])) && ($_GET['page'] == 'webp_express_settings_page')) {
21
- add_action('admin_enqueue_scripts', function () {
22
- include __DIR__ . '/enqueue_scripts.php';
23
- });
24
- }
25
-
26
  function webp_express_settings_page_content()
27
  {
 
28
  include __DIR__ . '/page.php';
29
  }
 
 
 
 
 
 
 
 
 
 
 
 
16
  include __DIR__ . '/submit.php';
17
  });
18
 
 
 
 
 
 
 
 
19
  function webp_express_settings_page_content()
20
  {
21
+ //include __DIR__ . '/enqueue_scripts.php'; // we don't do that, because our inline scripts would not be written in head on wordpress 4.4 and below
22
  include __DIR__ . '/page.php';
23
  }
24
+
25
+ function webp_express_admin_init() {
26
+
27
+ global $pagenow;
28
+ if ( ( 'options-general.php' === $pagenow ) && ( isset( $_GET['page'] ) ) && ( 'webp_express_settings_page' === $_GET['page'] ) ) {
29
+ add_action( 'admin_enqueue_scripts', function () {
30
+ include __DIR__ . '/enqueue_scripts.php';
31
+ } );
32
+ }
33
+ }
34
+
35
+ add_action( 'admin_init', 'webp_express_admin_init');
lib/options/options/cache-control.inc ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/{converter-options → options/converter-options}/cwebp.php RENAMED
File without changes
lib/options/{converter-options → options/converter-options}/ewww.php RENAMED
File without changes
lib/options/{converter-options → options/converter-options}/gd.php RENAMED
File without changes
lib/options/{converter-options → options/converter-options}/imagick.php RENAMED
File without changes
lib/options/{converter-options → options/converter-options}/imagickbinary.php RENAMED
File without changes
lib/options/{converter-options → options/converter-options}/wpc.php RENAMED
File without changes
lib/options/options/converters.inc ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Converters
3
+ // --------------------
4
+
5
+ echo '<tr><th scope="row">Conversion method';
6
+ echo helpIcon('Drag to reorder. The conversion method on top will first be tried. ' .
7
+ 'Should it fail, the next will be used, etc. To learn more about the conversion methods, <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a>');
8
+
9
+ echo '</th><td>';
10
+
11
+ //$converters = $config['converters'];
12
+ //echo '<script>window.converters = ' . json_encode($converters) . '</script>';
13
+ //echo '<script>window.defaultConverters = ' . json_encode($defaultConverters) . '</script>';
14
+
15
+ echo "<input type='text' name='converters' value='' style='visibility:hidden; height:0' />";
16
+
17
+ // https://premium.wpmudev.org/blog/handling-form-submissions/
18
+
19
+
20
+ ?>
21
+ <?php
22
+ /*
23
+ $localConverters = ['cwebp', 'imagick', 'gd'];
24
+ $testResult = WebPExpressHelpers::testConverters($localConverters);
25
+ //print_r($testResult);
26
+
27
+ if ($testResult['numOperationalConverters'] == 0) {
28
+ echo 'Unfortunately, your server is currently not able to convert to webp files by itself. You will need to set up a cloud converter.<br><br>';
29
+ foreach ($testResult['results'] as $result) {
30
+ echo $result['converter'] . ':' . $result['message'] . '<br>';
31
+ }
32
+ } else {
33
+ //echo 'Your server is able to convert webp files by itself.';
34
+ }
35
+ if ($testResult['numOperationalConverters'] == 1) {
36
+ //
37
+ }
38
+ */
39
+
40
+
41
+ /*
42
+ http://php.net/manual/en/function.set-include-path.php
43
+
44
+ //exec('/usr/sbin/getsebool -a', $output6, $returnCode5); // ok
45
+ //echo 'All se bools: ' . print_r($output6, true) . '. Return code:' . $returnCode5;
46
+ */
47
+
48
+ //echo '<h2>Conversion methods to try</h2>';
49
+ $dragIcon = '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="17px" height="17px" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#444444" stroke="none"><path d="M415 920 l-80 -80 165 0 165 0 -80 80 c-44 44 -82 80 -85 80 -3 0 -41 -36 -85 -80z"/><path d="M0 695 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M0 500 l0 -40 500 0 500 0 0 40 0 40 -500 0 -500 0 0 -40z"/><path d="M0 305 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M418 78 l82 -83 82 83 83 82 -165 0 -165 0 83 -82z"/></g></svg>';
50
+
51
+ /*echo '<p><i>Drag to reorder. The conversion method on top will first be tried. ';
52
+ echo 'Should it fail, the next will be used, etc.<br>';
53
+ echo 'To learn more about the conversion methods, ';
54
+ echo '<a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a></i></p>';
55
+ */
56
+ // https://github.com/RubaXa/Sortable
57
+
58
+ // Empty list of converters. The list will be populated by the javascript
59
+
60
+ function webp_express_printUpdateButtons() {
61
+ ?>
62
+ <button onclick="updateConverterOptionsAndSave()" class="button button-primary" type="button">Update and save settings</button>
63
+ <button onclick="updateConverterOptions()" class="button button-secondary" type="button">Update, but do not save yet</button>
64
+ <?php
65
+ //echo '<a href="javascript: tb_remove();">close</a>';
66
+ }
67
+ echo '<ul id="converters" style="margin-top: -13px"></ul>';
68
+
69
+ include 'converter-options/cwebp.php';
70
+ include 'converter-options/gd.php';
71
+ include 'converter-options/imagick.php';
72
+ include 'converter-options/ewww.php';
73
+ include 'converter-options/wpc.php';
74
+ include 'converter-options/imagickbinary.php';
75
+ ?>
76
+ </td></tr>
lib/options/options/do-not-pass-source-path-in-query-string.inc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/metadata.inc ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Metadata
3
+ // --------------------
4
+ //$maxQuality = get_option('webp_express_max_quality');
5
+ $metadata = $config['metadata'];
6
+
7
+ echo '<tr><th scope="row">Metadata';
8
+ echo helpIcon('Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, as it is not possible to copy the metadata with the Gd extension. Imagickbinary also currently lacks support');
9
+ echo '</th><td>';
10
+
11
+ echo '<select name="metadata">';
12
+ echo '<option value="none"' . ($metadata == 'none' ? ' selected' : '') . '>No metadata in webp</option>';
13
+ echo '<option value="all"' . ($metadata == 'all' ? ' selected' : '') . '>Copy all metadata to webp</option>';
14
+ echo '</select>';
15
+ echo '</td></tr>';
16
+ // echo '<tr><td colspan=2><p>Converted jpeg images will get same quality as original, but not more than this setting. Something between 70-85 is recommended for most websites.</p></td></tr>';
lib/options/options/quality.inc ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Quality
3
+ // --------------------
4
+
5
+ if ($canDetectQuality) {
6
+ echo '<tr><th scope="row">Quality';
7
+ echo helpIcon('If "Auto" is selected, the converted image will get same quality as source. Auto is recommended!');
8
+ echo '</th><td>';
9
+ $qualityAuto = $config['quality-auto'];;
10
+ echo '<select id="quality_auto_select" name="quality-auto">';
11
+ echo '<option value="auto_on"' . ($qualityAuto ? ' selected' : '') . '>Auto</option>';
12
+ echo '<option value="auto_off"' . (!$qualityAuto ? ' selected' : '') . '>Specific value</option>';
13
+ echo '</select>';
14
+
15
+ echo '</td></tr>';
16
+
17
+
18
+ // Max quality
19
+ // --------------------
20
+ $maxQuality = $config['max-quality'];
21
+
22
+ echo '<tr id="max_quality_row"><th scope="row">Max quality (0-100)';
23
+ echo helpIcon('Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
24
+ 'This option allows you to limit the quality to whatever is lowest: ' .
25
+ 'the quality of the source or max quality. Recommended value: Somewhere between 50-85');
26
+ echo '</th><td>';
27
+
28
+ echo '<input type="text" size=3 id="max_quality" name="max-quality" value="' . $maxQuality . '">';
29
+ echo '</td></tr>';
30
+ } else {
31
+
32
+ }
33
+
34
+ // Quality - specific
35
+ // --------------------
36
+ $qualitySpecific = $config['quality-specific'];
37
+
38
+ echo '<tr id="quality_specific_row"><th scope="row">Quality (0-100)';
39
+ if ($canDetectQuality) {
40
+ echo helpIcon('All converted images will be encoded with this quality');
41
+ } else {
42
+ echo helpIcon('All converted images will be encoded with this quality. ' .
43
+ 'For Remote WebP Express and Imagick, you however have the option to use override this, and use ' .
44
+ '"auto". With some setup, you can get quality detection working and you will then be able to set ' .
45
+ 'quality to "auto" generally. For that you either need to get the imagick extension running ' .
46
+ '(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
47
+ );
48
+ }
49
+ echo '</th><td>';
50
+
51
+ echo '<input type="text" size=3 id="quality_specific" name="quality-specific" value="' . $qualitySpecific . '">';
52
+ echo '</td></tr>';
lib/options/options/redirect-to-existing.inc ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
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/response-on-failure.inc ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Response on failure
3
+ // --------------------
4
+ echo '<tr><th scope="row">Response on failure';
5
+ echo helpIcon('Determines what to serve in case the image conversion should fail.');
6
+ echo '</th><td>';
7
+
8
+ //$fail = get_option('webp_express_failure_response');
9
+ $fail = $config['fail'];
10
+ echo '<select name="fail">';
11
+ echo '<option value="original"' . ($fail == 'original' ? ' selected' : '') . '>Original image</option>';
12
+ echo '<option value="404"' . ($fail == '404' ? ' selected' : '') . '>404</option>';
13
+ echo '<option value="report"' . ($fail == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
14
+ echo '<option value="report-as-image"' . ($fail == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
15
+ echo '</select>';
16
+ echo '</td></tr>';
17
+ // echo '<tr><td colspan=2>Determines what the converter should serve, in case the image conversion should fail. For production servers, recommended value is "Original image". For development servers, choose anything you like, but that</td></tr>';
lib/options/options/web-service.inc ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ include_once __DIR__ . '/../../classes/Paths.php';
3
+ use \WebPExpress\Paths;
4
+
5
+ //$whitelist = $config['web-service']['whitelist'];
6
+ //echo '<script>window.whitelist = ' . json_encode($whitelist) . '</script>';
7
+ ?>
8
+ <tr id="share">
9
+ <th scope="row">Enable web service?<?php echo helpIcon('Enabling the web service will allow selected sites to convert webp-images through this site (more options will appear, if you enable)'); ?></th>
10
+ <td>
11
+ <input type="checkbox" id="web_service_enabled" name="web-service-enabled" value="true" <?php echo ($config['web-service']['enabled'] ? 'checked="checked"' : '') ?>>
12
+ <input type='text' name='whitelist' id='whitelist' value='' style='visibility:hidden; height:0' />
13
+ <div id="whitelist_div"></div>
14
+ <div id="whitelist_properties_popup" class="das-popup">
15
+ <h3 class="hide-in-edit">Authorize website</h3>
16
+ <h3 class="hide-in-add">Edit authorized website</h3>
17
+ <input type="hidden" id="whitelist_uid">
18
+ <input type="hidden" id="whitelist_i">
19
+ <div>
20
+ <label for="whitelist_label">
21
+ Label
22
+ <?php echo helpIcon('The label is purely for your own reference'); ?>
23
+ </label>
24
+ <input id="whitelist_label" type="text">
25
+ </div>
26
+ <div>
27
+ <label for="whitelist_ip">
28
+ IP
29
+ <?php echo helpIcon('IP to allow access to service. You can use *, ie "212.91.*", or even "*"'); ?>
30
+ </label>
31
+ <input id="whitelist_ip" type="text">
32
+ </div>
33
+ <div>
34
+ <label for="whitelist_api_key">
35
+ Api key
36
+ <?php echo helpIcon('Who says api keys must be dull-looking meaningless sequences of random ' .
37
+ 'characters? Here you get to shape your key to your liking. Enter any phrase you want'); ?>
38
+ </label>
39
+ <input id="whitelist_api_key" type="password" class="hide-in-edit">
40
+ <a href="javascript:whitelistChangeApiKey()" class="hide-in-add" style="line-height:34px">Change api key</a>
41
+ </div>
42
+ <div>
43
+ <label for="whitelist_require_api_key_to_be_crypted_in_transfer">
44
+ Require api-key to be crypted in transfer?
45
+ <?php echo helpIcon('If checked, the web service will only accept crypted api keys. Crypting the api-key protects it from being stolen during transfer. On a few older server setups, clients do not have the capability to crypt'); ?>
46
+ </label>
47
+ <input id="whitelist_require_api_key_to_be_crypted_in_transfer" type="checkbox">
48
+ </div>
49
+ <p style="margin-top: 15px">Psst: The endpoint of the web service is: <b><?php echo Paths::getWebServiceUrl() ?></b></p>
50
+ <button id="whitelist_properties_add_button" onclick="whitelistAddWhitelistEntry()" class="hide-in-edit button button-primary" type="button" style="position:absolute; bottom:20px">
51
+ Add
52
+ </button>
53
+ <button id="whitelist_properties_update_button" onclick="whitelistUpdateWhitelistEntry()" class="hide-in-add button button-primary" type="button" style="position:absolute; bottom:20px">
54
+ Update
55
+ </button>
56
+ </div>
57
+ </td>
58
+ </tr>
lib/options/page.php CHANGED
@@ -43,8 +43,6 @@ function webpexpress_converterName($converterId) {
43
  return $converterId;
44
  }
45
 
46
- $canDetectQuality = TestRun::isLocalQualityDetectionWorking();
47
-
48
  function printAutoQualityOptionForConverter($converterId) {
49
  ?>
50
  <div>
@@ -68,18 +66,16 @@ function printAutoQualityOptionForConverter($converterId) {
68
  }
69
  //update_option('webp-express-migration-version', '1');
70
 
71
- // Test converters
72
  $testResult = TestRun::getConverterStatus();
73
- $workingConverters = [];
74
- if ($testResult) {
75
- $workingConverters = $testResult['workingConverters'];
76
- //print_r($testResult);
77
- } else {
78
  Messenger::printMessage(
79
  'error',
80
  'WebP Express cannot save a test conversion, because it does not have write ' .
81
  'access to your upload folder, nor your wp-content folder. Please provide!'
82
- );
83
  }
84
 
85
 
@@ -92,165 +88,20 @@ foreach (Paths::getHTAccessDirs() as $dir) {
92
  }*/
93
 
94
 
95
- $defaultConfig = [
96
- 'cache-control' => 'no-header',
97
- 'cache-control-custom' => 'public, max-age:3600',
98
- 'converters' => [],
99
- 'fail' => 'original',
100
- 'forward-query-string' => true,
101
- 'image-types' => 1,
102
- 'quality-auto' => $canDetectQuality,
103
- 'max-quality' => 80,
104
- 'quality-specific' => 70,
105
- 'metadata' => 'none',
106
- 'web-service' => [
107
- 'enabled' => false,
108
- 'whitelist' => [
109
- /*[
110
- 'uid' => '', // for internal purposes
111
- 'label' => '', // ie website name. It is just for display
112
- 'ip' => '', // restrict to these ips. * pattern is allowed.
113
- 'api-key' => '', // Api key for the entry. Not neccessarily unique for the entry
114
- //'quota' => 60
115
- ]
116
- */
117
- ]
118
-
119
- ]
120
- ];
121
-
122
- $defaultConverters = ConvertersHelper::$defaultConverters;
123
-
124
-
125
- $config = Config::loadConfig();
126
- //echo '<pre>' . print_r($config, true) . '</pre>';
127
- if (!$config) {
128
- $config = [];
129
- }
130
- //$config = [];
131
-
132
- $config = array_merge($defaultConfig, $config);
133
- if ($config['converters'] == null) {
134
- $config['converters'] = [];
135
- }
136
- if (!isset($config['web-service'])) {
137
- $config['web-service'] = [];
138
- }
139
- if (!isset($config['web-service']['whitelist'])) {
140
- $config['web-service']['whitelist'] = [];
141
- }
142
-
143
- // Remove keys in whitelist (so they cannot easily be picked up by examining the html)
144
- foreach ($config['web-service']['whitelist'] as &$whitelistEntry) {
145
- unset($whitelistEntry['api-key']);
146
- }
147
-
148
- // Remove keys from WPC converters
149
- foreach ($config['converters'] as &$converter) {
150
- if (isset($converter['converter']) && ($converter['converter'] == 'wpc')) {
151
- if (isset($converter['options']['api-key'])) {
152
- if ($converter['options']['api-key'] != '') {
153
- $converter['options']['_api-key-non-empty'] = true;
154
- }
155
- unset($converter['options']['api-key']);
156
- }
157
- }
158
- }
159
-
160
-
161
- if (count($config['converters']) == 0) {
162
- // This is first time visit!
163
-
164
- if (count($workingConverters) == 0) {
165
- // No converters are working
166
- // Send ewww converter to top
167
- $resultPart1 = [];
168
- $resultPart2 = [];
169
- foreach ($defaultConverters as $converter) {
170
- $converterId = $converter['converter'];
171
- if ($converterId == 'ewww') {
172
- $resultPart1[] = $converter;
173
- } else {
174
- $resultPart2[] = $converter;
175
- }
176
- }
177
- $config['converters'] = array_merge($resultPart1, $resultPart2);
178
- } else {
179
- // Send converters not working to the bottom
180
- // - and also deactivate them..
181
- $resultPart1 = [];
182
- $resultPart2 = [];
183
- foreach ($defaultConverters as $converter) {
184
- $converterId = $converter['converter'];
185
- if (in_array($converterId, $workingConverters)) {
186
- $resultPart1[] = $converter;
187
- } else {
188
- $converter['deactivated'] = true;
189
- $resultPart2[] = $converter;
190
- }
191
- }
192
- $config['converters'] = array_merge($resultPart1, $resultPart2);
193
- }
194
-
195
- // $workingConverters
196
- //echo '<pre>' . print_r($converters, true) . '</pre>';
197
- } else {
198
- // not first time visit...
199
- // merge missing converters in
200
- $config['converters'] = ConvertersHelper::mergeConverters($config['converters'], ConvertersHelper::$defaultConverters);
201
- }
202
-
203
-
204
- // Set "working" and "error" properties
205
- if ($testResult) {
206
- foreach ($config['converters'] as &$converter) {
207
- $converterId = $converter['converter'];
208
- $hasError = isset($testResult['errors'][$converterId]);
209
- $working = !$hasError;
210
- if (isset($converter['working']) && ($converter['working'] != $working)) {
211
- if ($working) {
212
- Messenger::printMessage(
213
- 'info',
214
- 'Hurray! - The <i>' . webpexpress_converterName($converterId) . '</i> conversion method is working now!'
215
- );
216
- } else {
217
- Messenger::printMessage(
218
- 'warning',
219
- 'Sad news. The <i>' . webpexpress_converterName($converterId) . '</i> conversion method is not working anymore. What happened?'
220
- );
221
- }
222
- }
223
- $converter['working'] = $working;
224
- if ($hasError) {
225
- $error = $testResult['errors'][$converterId];
226
- if ($converterId == 'wpc') {
227
- if (preg_match('/Missing URL/', $error)) {
228
- $error = 'Not configured';
229
- }
230
- if ($error == 'No remote host has been set up') {
231
- $error = 'Not configured';
232
- }
233
-
234
- if (preg_match('/cloud service is not enabled/', $error)) {
235
- $error = 'The server is not enabled. Click the "Enable web service" on WebP Express settings on the site you are trying to connect to.';
236
- }
237
- }
238
- $converter['error'] = $error;
239
- } else {
240
- unset($converter['error']);
241
- }
242
- }
243
- }
244
  //echo '<pre>' . print_r($config['converters'], true) . '</pre>';
245
 
246
  //echo 'Working converters:' . print_r($workingConverters, true) . '<br>';
247
  // Generate a custom nonce value.
248
  $webpexpress_settings_nonce = wp_create_nonce('webpexpress_settings_nonce');
249
  ?>
250
- <p>
251
- <i>WebP Express takes care of serving autogenerated WebP images instead of jpeg/png to browsers that supports WebP.<br>
252
- The settings below does not affect your original images - only the converted webp images, and the redirection rules.</i>
253
- </p>
 
 
 
 
254
 
255
  <?php
256
 
@@ -261,388 +112,73 @@ echo '<form id="webpexpress_settings" action="' . esc_url( admin_url( 'admin-pos
261
  <input type="hidden" name="action" value="webpexpress_settings_submit">
262
  <input type="hidden" name="webpexpress_settings_nonce" value="<?php echo $webpexpress_settings_nonce ?>" />
263
 
 
 
 
 
 
 
 
 
264
  <?php
265
 
266
  function helpIcon($text) {
267
  return '<div class="help">?<div class="popup">' . $text . '</div></div>';
268
  }
269
- echo '<table class="form-table"><tbody>';
270
-
271
- // Image types
272
- // ------------
273
- echo '<tr><th scope="row">Image types to convert';
274
- 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>');
275
- echo '</th><td>';
276
-
277
- // bitmask
278
- // 1: JPEGs
279
- // 2: PNG's
280
- // Converting only jpegs is thus "1"
281
- // Converting both jpegs and pngs is (1+2) = 3
282
- //$imageTypes = get_option('webp_express_image_types_to_convert');
283
- $imageTypes = $config['image-types'];
284
-
285
- echo '<select name="image-types">';
286
- echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>Do not convert any images!</option>';
287
- echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only convert jpegs</option>';
288
- echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Convert both jpegs and pngs</option>';
289
- echo '</select>';
290
-
291
- echo '</td></tr>';
292
-
293
- // Quality
294
- // --------------------
295
-
296
- if ($canDetectQuality) {
297
- echo '<tr><th scope="row">Quality';
298
- echo helpIcon('If "Auto" is selected, the converted image will get same quality as source. Auto is recommended!');
299
- echo '</th><td>';
300
- $qualityAuto = $config['quality-auto'];;
301
- echo '<select id="quality_auto_select" name="quality-auto">';
302
- echo '<option value="auto_on"' . ($qualityAuto ? ' selected' : '') . '>Auto</option>';
303
- echo '<option value="auto_off"' . (!$qualityAuto ? ' selected' : '') . '>Specific value</option>';
304
- echo '</select>';
305
-
306
- echo '</td></tr>';
307
-
308
-
309
- // Max quality
310
- // --------------------
311
- $maxQuality = $config['max-quality'];
312
-
313
- echo '<tr id="max_quality_row"><th scope="row">Max quality (0-100)';
314
- echo helpIcon('Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
315
- 'This option allows you to limit the quality to whatever is lowest: ' .
316
- 'the quality of the source or max quality. Recommended value: Somewhere between 50-85');
317
- echo '</th><td>';
318
-
319
- echo '<input type="text" size=3 id="max_quality" name="max-quality" value="' . $maxQuality . '">';
320
- echo '</td></tr>';
321
- } else {
322
-
323
- }
324
-
325
- // Quality - specific
326
- // --------------------
327
- $qualitySpecific = $config['quality-specific'];
328
-
329
- echo '<tr id="quality_specific_row"><th scope="row">Quality (0-100)';
330
- if ($canDetectQuality) {
331
- echo helpIcon('All converted images will be encoded with this quality');
332
- } else {
333
- echo helpIcon('All converted images will be encoded with this quality. ' .
334
- 'For Remote WebP Express and Imagick, you however have the option to use override this, and use ' .
335
- '"auto". With some setup, you can get quality detection working and you will then be able to set ' .
336
- 'quality to "auto" generally. For that you either need to get the imagick extension running ' .
337
- '(PECL >= 2.2.2) or exec() rights and either imagick or gmagick installed.'
338
- );
339
- }
340
- echo '</th><td>';
341
-
342
- echo '<input type="text" size=3 id="quality_specific" name="quality-specific" value="' . $qualitySpecific . '">';
343
- echo '</td></tr>';
344
-
345
- // Converters
346
- // --------------------
347
-
348
- echo '<tr><th scope="row">Conversion method';
349
- echo helpIcon('Drag to reorder. The conversion method on top will first be tried. ' .
350
- 'Should it fail, the next will be used, etc. To learn more about the conversion methods, <a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a>');
351
-
352
- echo '</th><td>';
353
-
354
- $converters = $config['converters'];
355
- echo '<script>window.converters = ' . json_encode($converters) . '</script>';
356
- echo '<script>window.defaultConverters = ' . json_encode($defaultConverters) . '</script>';
357
-
358
- echo "<input type='text' name='converters' value='' style='visibility:hidden; height:0' />";
359
-
360
- // https://premium.wpmudev.org/blog/handling-form-submissions/
361
-
362
-
363
- ?>
364
- <?php
365
- /*
366
- $localConverters = ['cwebp', 'imagick', 'gd'];
367
- $testResult = WebPExpressHelpers::testConverters($localConverters);
368
- //print_r($testResult);
369
-
370
- if ($testResult['numOperationalConverters'] == 0) {
371
- echo 'Unfortunately, your server is currently not able to convert to webp files by itself. You will need to set up a cloud converter.<br><br>';
372
- foreach ($testResult['results'] as $result) {
373
- echo $result['converter'] . ':' . $result['message'] . '<br>';
374
- }
375
- } else {
376
- //echo 'Your server is able to convert webp files by itself.';
377
- }
378
- if ($testResult['numOperationalConverters'] == 1) {
379
- //
380
- }
381
- */
382
-
383
-
384
- /*
385
- http://php.net/manual/en/function.set-include-path.php
386
-
387
- //exec('/usr/sbin/getsebool -a', $output6, $returnCode5); // ok
388
- //echo 'All se bools: ' . print_r($output6, true) . '. Return code:' . $returnCode5;
389
- */
390
-
391
- //echo '<h2>Conversion methods to try</h2>';
392
- $dragIcon = '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="17px" height="17px" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#444444" stroke="none"><path d="M415 920 l-80 -80 165 0 165 0 -80 80 c-44 44 -82 80 -85 80 -3 0 -41 -36 -85 -80z"/><path d="M0 695 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M0 500 l0 -40 500 0 500 0 0 40 0 40 -500 0 -500 0 0 -40z"/><path d="M0 305 l0 -45 500 0 500 0 0 45 0 45 -500 0 -500 0 0 -45z"/><path d="M418 78 l82 -83 82 83 83 82 -165 0 -165 0 83 -82z"/></g></svg>';
393
-
394
- /*echo '<p><i>Drag to reorder. The conversion method on top will first be tried. ';
395
- echo 'Should it fail, the next will be used, etc.<br>';
396
- echo 'To learn more about the conversion methods, ';
397
- echo '<a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a></i></p>';
398
- */
399
- // https://github.com/RubaXa/Sortable
400
-
401
- // Empty list of converters. The list will be populated by the javascript
402
-
403
- function webp_express_printUpdateButtons() {
404
- ?>
405
- <button onclick="updateConverterOptionsAndSave()" class="button button-primary" type="button">Update and save settings</button>
406
- <button onclick="updateConverterOptions()" class="button button-secondary" type="button">Update, but do not save yet</button>
407
- <?php
408
- //echo '<a href="javascript: tb_remove();">close</a>';
409
- }
410
- echo '<ul id="converters" style="margin-top: -13px"></ul>';
411
-
412
- include 'converter-options/cwebp.php';
413
- include 'converter-options/gd.php';
414
- include 'converter-options/imagick.php';
415
- include 'converter-options/ewww.php';
416
- include 'converter-options/wpc.php';
417
- include 'converter-options/imagickbinary.php';
418
  ?>
419
- </td></tr>
420
- <?php
421
-
422
- // echo '<tr><td colspan=2><p>Converted jpeg images will get same quality as original, but not more than this setting. Something between 70-85 is recommended for most websites.</p></td></tr>';
423
-
424
- // method
425
- //echo '<p>When higher values are used, the encoder will spend more time inspecting additional encoding possibilities and decide on the quality gain. Supported by cwebp, wpc and imagick</p>';
426
-
427
- // Cache-Control
428
- // --------------------
429
- //$maxQuality = get_option('webp_express_max_quality');
430
- $cacheControl = $config['cache-control'];
431
- $cacheControlCustom = $config['cache-control-custom'];
432
-
433
- echo '<tr><th scope="row">Caching';
434
- echo helpIcon(
435
- 'Controls the cache-control header for the converted image. ' .
436
- 'This header is only sent when a converted image is successfully delivered (either existing, or new ' .
437
- 'conversion). In case of failure, headers will be sent to prevent caching.');
438
- echo '</th><td>';
439
- echo '<select id="cache_control_select" name="cache-control">';
440
- echo '<option value="no-header"' . ($cacheControl == 'no-header' ? ' selected' : '') . '>Do not set Cache-Control header</option>';
441
- echo '<option value="one-second"' . ($cacheControl == 'one-second' ? ' selected' : '') . '>One second</option>';
442
- echo '<option value="one-minute"' . ($cacheControl == 'one-minute' ? ' selected' : '') . '>One minute</option>';
443
- echo '<option value="one-hour"' . ($cacheControl == 'one-hour' ? ' selected' : '') . '>One hour</option>';
444
- echo '<option value="one-day"' . ($cacheControl == 'one-day' ? ' selected' : '') . '>One day</option>';
445
- echo '<option value="one-week"' . ($cacheControl == 'one-week' ? ' selected' : '') . '>One week</option>';
446
- echo '<option value="one-month"' . ($cacheControl == 'one-month' ? ' selected' : '') . '>One month</option>';
447
- echo '<option value="one-year"' . ($cacheControl == 'one-year' ? ' selected' : '') . '>One year</option>';
448
- echo '<option value="custom"' . ($cacheControl == 'custom' ? ' selected' : '') . '>Custom Cache-Control header</option>';
449
- echo '</select><br>';
450
- echo '<input type="text" id="cache_control_custom" name="cache-control-custom" value="' . $cacheControlCustom . '">';
451
- echo '</td></tr>';
452
-
453
-
454
- // Metadata
455
- // --------------------
456
- //$maxQuality = get_option('webp_express_max_quality');
457
- $metadata = $config['metadata'];
458
-
459
- echo '<tr><th scope="row">Metadata';
460
- echo helpIcon('Decide what to do with image metadata, such as Exif. Note that this setting is not supported by the "Gd" conversion method, as it is not possible to copy the metadata with the Gd extension');
461
- echo '</th><td>';
462
-
463
- echo '<select name="metadata">';
464
- echo '<option value="none"' . ($metadata == 'none' ? ' selected' : '') . '>No metadata in webp</option>';
465
- echo '<option value="all"' . ($metadata == 'all' ? ' selected' : '') . '>Copy all metadata to webp</option>';
466
- echo '</select>';
467
- echo '</td></tr>';
468
- // echo '<tr><td colspan=2><p>Converted jpeg images will get same quality as original, but not more than this setting. Something between 70-85 is recommended for most websites.</p></td></tr>';
469
-
470
- // method
471
- //echo '<p>When higher values are used, the encoder will spend more time inspecting additional encoding possibilities and decide on the quality gain. Supported by cwebp, wpc and imagick</p>';
472
-
473
- // Response on failure
474
- // --------------------
475
- echo '<tr><th scope="row">Response on failure';
476
- echo helpIcon('Determines what to serve in case the image conversion should fail.');
477
- echo '</th><td>';
478
-
479
- //$fail = get_option('webp_express_failure_response');
480
- $fail = $config['fail'];
481
- echo '<select name="fail">';
482
- echo '<option value="original"' . ($fail == 'original' ? ' selected' : '') . '>Original image</option>';
483
- echo '<option value="404"' . ($fail == '404' ? ' selected' : '') . '>404</option>';
484
- echo '<option value="report"' . ($fail == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
485
- echo '<option value="report-as-image"' . ($fail == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
486
- echo '</select>';
487
- echo '</td></tr>';
488
- // echo '<tr><td colspan=2>Determines what the converter should serve, in case the image conversion should fail. For production servers, recommended value is "Original image". For development servers, choose anything you like, but that</td></tr>';
489
-
490
- //echo '</tbody></table>';
491
-
492
-
493
- // Web Service
494
- // --------------------
495
-
496
- $whitelist = $config['web-service']['whitelist'];
497
- echo '<script>window.whitelist = ' . json_encode($whitelist) . '</script>';
498
- echo '<tr id="share"><th scope="row">Enable web service?';
499
- echo helpIcon('Enabling the web service will allow selected sites to convert webp-images through this site (more options will appear, if you enable)');
500
- echo '</th><td>';
501
-
502
- echo '<input type="checkbox" id="web_service_enabled" name="web-service-enabled" value="true" ' . ($config['web-service']['enabled'] ? 'checked="checked"' : '') . '">';
503
- echo "<input type='text' name='whitelist' id='whitelist' value='' style='visibility:hidden; height:0' />";
504
 
505
- ?>
506
- <div id="whitelist_div"></div>
507
- <!--
508
- <div id="whitelist_listen_popup" class="das-popup">
509
- <h3>Listening for a request<span class="animated-dots">...</span></h3>
510
- <div style="font-size:90%">
511
- Send the instructions below to the one that controls the website that you want to grant access.
512
- If you control that website, simply open up a new tab and perform the following:
513
- <ol>
514
- <li>Log in to the website you want to use the web service</li>
515
- <li>In WebP Express settings, find the <i>Remote WebP Express</i> conversion method and click <i>configure</i></li>
516
- <li>Click "Make request"</li>
517
- <li>Enter this url: <b><?php echo Paths::getWebServiceUrl(); ?></b></li>
518
- </ol>
519
- This popup will close once the above is completed<br><br>
520
- </div>
521
- <div style="display: inline-block;vertical-align:middle; line-height:27px;">
522
- <button onclick="whitelistCancelListening()" class="button button-secondary" type="button">
523
- Give up
524
- </button>
525
- or
526
- <button onclick="whitelistAddManually()" class="button button-secondary" type="button">
527
- Add manually
528
- </button>
529
- </div>
530
- </div>
531
- -->
532
- <div id="whitelist_properties_popup" class="das-popup">
533
- <h3 class="hide-in-edit">Authorize website</h3>
534
- <h3 class="hide-in-add">Edit authorized website</h3>
535
- <input type="hidden" id="whitelist_uid">
536
- <input type="hidden" id="whitelist_i">
537
- <div>
538
- <label for="whitelist_label">
539
- Label
540
- <?php echo helpIcon('The label is purely for your own reference'); ?>
541
- </label>
542
- <input id="whitelist_label" type="text">
543
- </div>
544
- <div>
545
- <label for="whitelist_ip">
546
- IP
547
- <?php echo helpIcon('IP to allow access to service. You can use *, ie "212.91.*", or even "*"'); ?>
548
- </label>
549
- <input id="whitelist_ip" type="text">
550
- </div>
551
- <div>
552
- <label for="whitelist_api_key">
553
- Api key
554
- <?php echo helpIcon('Who says api keys must be dull-looking meaningless sequences of random ' .
555
- 'characters? Here you get to shape your key to your liking. Enter any phrase you want'); ?>
556
- </label>
557
- <input id="whitelist_api_key" type="password" class="hide-in-edit">
558
- <a href="javascript:whitelistChangeApiKey()" class="hide-in-add" style="line-height:34px">Change api key</a>
559
- </div>
560
- <div>
561
- <label for="whitelist_require_api_key_to_be_crypted_in_transfer">
562
- Require api-key to be crypted in transfer?
563
- <?php echo helpIcon('If checked, the web service will only accept crypted api keys. Crypting the api-key protects it from being stolen during transfer. On a few older server setups, clients do not have the capability to crypt'); ?>
564
- </label>
565
- <input id="whitelist_require_api_key_to_be_crypted_in_transfer" type="checkbox">
566
- </div>
567
- <p style="margin-top: 15px">Psst: The endpoint of the web service is: <b><?php echo Paths::getWebServiceUrl() ?></b></p>
568
-
569
- <button id="whitelist_properties_add_button" onclick="whitelistAddWhitelistEntry()" class="hide-in-edit button button-primary" type="button" style="position:absolute; bottom:20px">
570
- Add
571
- </button>
572
- <button id="whitelist_properties_update_button" onclick="whitelistUpdateWhitelistEntry()" class="hide-in-add button button-primary" type="button" style="position:absolute; bottom:20px">
573
- Update
574
- </button>
575
- </div>
576
- <!--
577
- <div id="whitelist_accept_request" class="das-popup">
578
- <h3>Incoming request!</h3>
579
- <div id="request_details"></div>
580
- <button onclick="whitelistAcceptRequest()" class="button button-primary" type="button" style="position:absolute; bottom:20px">Grant access</button>
581
- <button onclick="whitelistDenyRequest()" class="button button-secondary" type="button" style="position:absolute; bottom:20px;right:20px">Deny</button>
582
- </div>-->
583
-
584
- <?php
585
- echo '</td></tr>';
586
 
587
 
588
- // WPC - whitelist
589
- // --------------------
590
- /*
591
- echo '<tr id="whitelist_row"><th scope="row">Whitelist';
592
 
593
- $whitelist = $config['server']['whitelist'];
594
-
595
- echo '<script>window.whitelist = ' . json_encode($whitelist) . '</script>';
596
- echo helpIcon('Specify which sites that may use the conversion service.');
597
- echo '</th><td>';
598
- */
599
- ?>
600
- <!--
601
- <div id="whitelist_enter_password_popup" style="display:none">
602
- <div class="whitelist-popup-content">
603
- <div>
604
- <label for="whitelist_password">New password</label>
605
- <input type="password" id="whitelist_enter_password">
606
- </div>
607
- <div>
608
- <label for="whitelist_hash_password">Scramble password?</label>
609
- <input type="checkbox" id="whitelist_hash_password">
610
- </div><br>
611
- <i>Note: If you choose "scramble password", the password will be scrambled.
612
- This protects others from discovering what you wrote as password.
613
- It however still allows people with read access to the file system of your website to get the scrambled
614
- password and use that to connect with.
615
- </i>
616
- <br><br>
617
- <button onclick="setPassword()" class="button button-primary" type="button">Set password</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
- </div>
620
- </div>
621
- -->
622
- <?php
623
- /*
624
- echo '<div id="whitelist_div"></div>';
625
- echo "<input type='text' name='whitelist' value='' style='visibility:hidden; height:0' />"; //
626
- //echo gethostbyaddr('212.97.134.33');
627
- //echo gethostbyname('www.rosell.dk');
628
- echo '<div id="password_helptext">' . helpIcon('You may have to leave blank, if the site in question doesnt have the md5() function available.<br><br>' .
629
- 'md5 is needed because the password is not transmitted directly, but used to create a ' .
630
- 'unique hash for the image being converted. So if someone intercepts, they will only get the hash, not the password. And that ' .
631
- 'hash will only work for that specific image.') . '</div>';visibility:
632
- echo '<div id="whitelist_site_helptext">' . helpIcon('Enter IP or domain (ie www.example.com). You may use * as a wildcard.') . '</div>';
633
- //echo '<div id="whitelist_quota_helptext">' . helpIcon('Maximum conversions per hour for this site') . '</div>';
634
-
635
- echo '</td></tr>';
636
- */
637
-
638
- ?>
639
- </tbody></table>
640
-
641
- <table>
642
- <tr>
643
- <td style="padding-right:20px"><?php submit_button('Save settings', 'primary', 'mysubmit'); ?></td>
644
- <td><?php submit_button('Save settings and force new .htaccess rules', 'secondary', 'force'); ?></td>
645
- </tr>
646
- </table>
647
  </form>
648
  </div>
43
  return $converterId;
44
  }
45
 
 
 
46
  function printAutoQualityOptionForConverter($converterId) {
47
  ?>
48
  <div>
66
  }
67
  //update_option('webp-express-migration-version', '1');
68
 
69
+ $canDetectQuality = TestRun::isLocalQualityDetectionWorking();
70
  $testResult = TestRun::getConverterStatus();
71
+ $config = Config::getConfigForOptionsPage();
72
+
73
+ if (!$testResult) {
 
 
74
  Messenger::printMessage(
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
 
88
  }*/
89
 
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  //echo '<pre>' . print_r($config['converters'], true) . '</pre>';
92
 
93
  //echo 'Working converters:' . print_r($workingConverters, true) . '<br>';
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
 
112
  <input type="hidden" name="action" value="webpexpress_settings_submit">
113
  <input type="hidden" name="webpexpress_settings_nonce" value="<?php echo $webpexpress_settings_nonce ?>" />
114
 
115
+ <fieldset class="block buttons">
116
+ <table>
117
+ <tr>
118
+ <td style="padding-right:20px"><?php submit_button('Save settings', 'primary', 'mysubmit'); ?></td>
119
+ <td><?php submit_button('Save settings and force new .htaccess rules', 'secondary', 'force'); ?></td>
120
+ </tr>
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>
lib/options/submit.php CHANGED
@@ -104,9 +104,11 @@ $config = [
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
- 'forward-query-string' => true,
108
  'image-types' => sanitize_text_field($_POST['image-types']),
109
  'metadata' => sanitize_text_field($_POST['metadata']),
 
 
 
110
  'web-service' => [
111
  'enabled' => isset($_POST['web-service-enabled']),
112
  'whitelist' => json_decode(wp_unslash($_POST['whitelist']), true)
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)
web-service/.htaccess ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <FilesMatch "wpc\.php$">
2
+ <IfModule !mod_authz_core.c>
3
+ Order deny,allow
4
+ Allow from all
5
+ </IfModule>
6
+ <IfModule mod_authz_core.c>
7
+ Require all granted
8
+ </IfModule>
9
+ </FilesMatch>
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.8.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.9.0
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
wod/.htaccess ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <FilesMatch "webp-on-demand\.php$">
2
+ <IfModule !mod_authz_core.c>
3
+ Order deny,allow
4
+ Allow from all
5
+ </IfModule>
6
+ <IfModule mod_authz_core.c>
7
+ Require all granted
8
+ </IfModule>
9
+ </FilesMatch>
wod/webp-on-demand.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
 
3
  //echo 'display errors:' . ini_get('display_errors');
4
  //exit;
@@ -7,52 +11,93 @@
7
  require '../vendor/rosell-dk/webp-convert/build/webp-on-demand-1.inc';
8
  //require '../vendor/autoload.php';
9
 
 
 
10
  use \WebPConvert\WebPConvert;
11
 
12
- $options = [];
 
 
 
 
 
 
13
 
14
- $contentDirAbs = $_SERVER['DOCUMENT_ROOT'] . '/' . $_GET['wp-content'] . '/webp-express';
15
- $configFilename = $contentDirAbs . '/config/wod-options.json';
16
- $handle = @fopen($configFilename, "r");
17
- $json = fread($handle, filesize($configFilename));
18
- fclose($handle);
 
19
 
20
- $options = json_decode($json, true);
21
- //echo '<pre>' . print_r($options, true) . '</pre>';
22
- //exit;
23
 
24
- $options['require-for-conversion'] = 'webp-on-demand-2.inc';
25
- //$options['require-for-conversion'] = '../../../autoload.php';
 
 
 
 
26
 
27
- foreach ($options['converters'] as &$converter) {
28
- if (isset($converter['converter'])) {
29
- $converterId = $converter['converter'];
30
- } else {
31
- $converterId = $converter;
32
  }
33
- if ($converterId == 'cwebp') {
34
- $converter['options']['rel-path-to-precompiled-binaries'] = '../src/Converters/Binaries';
 
 
 
 
 
35
  }
36
- }
37
 
38
- if ($options['forward-query-string']) {
39
- if (isset($_GET['debug'])) {
40
- $options['show-report'] = true;
 
 
 
 
41
  }
42
- if (isset($_GET['reconvert'])) {
43
- $options['reconvert'] = true;
 
 
 
44
  }
 
 
 
 
45
  }
46
 
47
- if (isset($_GET['source'])) {
48
- $source = $_GET['source'];
49
- } elseif (isset($_GET['xsource'])) {
50
- $source = substr($_GET['xsource'], 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
 
 
 
 
53
  // Calculate destination
54
- $docRoot = rtrim($_SERVER["DOCUMENT_ROOT"], '/');
55
- $imageRoot = $contentDirAbs . '/webp-images';
56
 
57
  // Check if source is residing inside document root.
58
  // (it is, if path starts with document root + '/')
@@ -75,5 +120,34 @@ if (substr($source, 0, strlen($docRoot) + 1) === $docRoot . '/') {
75
  // If we wanted webp images to be located in same folder, with ".webp" extension:
76
  // $destination = preg_replace('/\.(jpg|jpeg|png)$/', '.webp', $source);
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
 
79
  WebPConvert::convertAndServe($source, $destination, $options);
1
  <?php
2
+ // https://www.askapache.com/htaccess/crazy-advanced-mod_rewrite-tutorial/#Decoding_Mod_Rewrite_Variables
3
+
4
+ ini_set('display_errors', 1);
5
+ error_reporting(E_ALL);
6
 
7
  //echo 'display errors:' . ini_get('display_errors');
8
  //exit;
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)) {
20
+ header('X-WebP-Express-Error: Configuration file not found!', true);
21
+ echo 'Configuration file not found!';
22
+ //WebPConvert::convertAndServe($source, $destination, []);
23
+ exit;
24
+ }
25
 
26
+ // TODO: Handle read error / json error
27
+ $handle = @fopen($configFilename, "r");
28
+ $json = fread($handle, filesize($configFilename));
29
+ fclose($handle);
30
+ return json_decode($json, true);
31
+ }
32
 
33
+ function getSource($allowInQS, $allowInHeader) {
34
+ //echo '<pre>' . print_r($_SERVER, true) . '</pre>'; exit;
 
35
 
36
+ // First check if it is in an environment variable - thats the safest way
37
+ foreach ($_SERVER as $key => $item) {
38
+ if (substr($key, -14) == 'REDIRECT_REQFN') {
39
+ return $item;
40
+ }
41
+ }
42
 
43
+ if ($allowInHeader) {
44
+ if (isset($_SERVER['HTTP_REQFN'])) {
45
+ return $_SERVER['HTTP_REQFN'];
46
+ }
 
47
  }
48
+
49
+ if ($allowInQS) {
50
+ if (isset($_GET['source'])) {
51
+ return $_GET['source']; // No url decoding needed as $_GET is already decoded
52
+ } elseif (isset($_GET['xsource'])) {
53
+ return substr($_GET['xsource'], 1);
54
+ }
55
  }
 
56
 
57
+ // Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
58
+ // correct result in all setups (ie "folder method 1")
59
+ $requestUriNoQS = explode('?', $_SERVER['REQUEST_URI'])[0];
60
+ $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
61
+ $source = $docRoot . urldecode($requestUriNoQS);
62
+ if (file_exists($source)) {
63
+ return $source;
64
  }
65
+
66
+ header('X-WebP-Express-Error: None of the available methods for locating source file works', true);
67
+ echo 'None of the available methods for locating source file works!';
68
+ if (!$allowInHeader) {
69
+ echo '<br>Have you tried allowing source to be passed as a request header?';
70
  }
71
+ if (!$allowInQS) {
72
+ echo '<br>Have you tried allowing source to be passed in querystring?';
73
+ }
74
+ exit;
75
  }
76
 
77
+ $docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
78
+ $wpContentDirRel = (isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content');
79
+ $webExpressContentDirRel = $wpContentDirRel . '/webp-express';
80
+ $webExpressContentDirAbs = $docRoot . '/' . $webExpressContentDirRel;
81
+ $configFilename = $webExpressContentDirAbs . '/config/wod-options.json';
82
+
83
+ $options = loadConfig($configFilename);
84
+
85
+ $allowInQS = !(isset($options['do-not-pass-source-in-query-string']) && $options['do-not-pass-source-in-query-string']);
86
+ $allowInHeader = true; // todo: implement setting
87
+ $source = getSource($allowInQS, $allowInHeader);
88
+ //$source = getSource(false, false);
89
+
90
+ if (!file_exists($source)) {
91
+ header('X-WebP-Express-Error: Source file not found!', true);
92
+ echo 'Source file not found!';
93
+ exit;
94
  }
95
 
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 + '/')
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
+
125
+
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'];
135
+ } else {
136
+ $converterId = $converter;
137
+ }
138
+ if ($converterId == 'cwebp') {
139
+ $converter['options']['rel-path-to-precompiled-binaries'] = '../src/Converters/Binaries';
140
+ }
141
+ }
142
+
143
+ if ($options['forward-query-string']) {
144
+ if (isset($_GET['debug'])) {
145
+ $options['show-report'] = true;
146
+ }
147
+ if (isset($_GET['reconvert'])) {
148
+ $options['reconvert'] = true;
149
+ }
150
+ }
151
 
152
+ //echo "<pre>source: $source \ndestination: $destination \n\noptions:" . print_r($options, true) . '</pre>'; exit;
153
  WebPConvert::convertAndServe($source, $destination, $options);