WebP Express - Version 0.6.0

Version Description

This version added option for setting caching header, fixed a serious issue with Imagick, added a new converter, Gmagick, added a great deal of options to Cwebp and generally improved the interface.

  • Added option for caching
  • Fixed long standing and serious issue with Imagick converter. It no longer generates webp images in poor quality
  • Added gmagick as a new conversion method
  • WebPExpress now runs on newly released WebP-Convert 1.2.0
  • Added many new options for cwebp
  • You can now quickly see converter status by hovering over a converter
  • You can now choose between having quality auto-detected or not (if the server supports detecting quality).
  • If the server does not support detecting quality, the WPC converter will display a quality "auto" option
  • Added special intro message for those who has no working conversion methods
  • Added help texts for options
  • Settings are now saved, when changing converter options. Too many times, I found myself forgetting to save...

For more info, see the closed issues on the 0.6.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.6.0

=

Download this release

Release Info

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

Code changes from version 0.5.0 to 0.6.0

.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ composer.lock
2
+ composer.phar
3
+ /vendor
README.md CHANGED
@@ -2,16 +2,18 @@
2
 
3
  Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library, galleries, theme images etc).
4
 
 
 
5
  ## Description
6
 
7
  This plugin let's you take advantage of the WebP image format with only a little effort. Install, configure, test, forget - and enjoy the increased performance of your website.
8
 
9
  The plugin basically routes jpeg/png images to an image converter, or - if the image converter has already converted the image - directly to a converted image. The approach has the benefit that is works regardless of how an image found its way into your server - be it Media Library, Galleries, or even theme images referenced with CSS.
10
 
11
- The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and some of the ecosystem around it: [WebPConvertAndServe](https://github.com/rosell-dk/webp-convert-and-serve) and [WebPOnDemand](https://github.com/rosell-dk/webp-on-demand)
12
 
13
  #### Benefits
14
- - Much faster load time on images in Chrome browsers. The converted images are typically less than half the size (for jpeg), while maintaining the same quality. Bear in mind that images typically are responsible for most of the bandwidth usage.
15
  - Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
16
  - Better ranking in Google searches (performance is taken into account by Google)
17
  - Less bandwidth consumption - makes a difference when abroad and in the parts of the world with slow and expensive internet connections.
@@ -47,20 +49,20 @@ Note that the plugin does not change any HTML. In the HTML the image src is stil
47
  - Reload the page
48
  - Find a jpeg or png image in the list. In the "type" column, it should say "webp"
49
 
50
- In order to test that the image is not being reconverted every time, look at the Response headers of the image. There should be a "X-WebP-On-Demand" header. It should say "Converting image (handed over to WebPConvertAndServe)" the first time, but "Serving existing converted image" on subsequent requests (WebP-Express is based upon [WebP On Demand](https://github.com/rosell-dk/webp-on-demand)). When routed to image converter, there should also be some headers beginning with "X-WebP-Convert-And-Serve", which reveals information about the conversion.
51
 
52
- You can also append `?debug` after any image url, in order to run a conversion, and see the conversion report. Btw: If you append `?reconvert` after an image url, you will force a reconversion of the image.
53
 
54
  ### Notes
55
 
56
  *Note:*
57
- The redirect rules created in *.htaccess* are pointing to the WebP on demand script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
58
 
59
  *Note:*
60
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
61
 
62
  *Note:*
63
- The plugin has not been tested in multisite configurations. It's on the roadmap!
64
 
65
 
66
  ## Limitations
@@ -71,8 +73,8 @@ The plugin has not been tested in multisite configurations. It's on the roadmap!
71
 
72
  ## Frequently Asked Questions
73
 
74
- ### How do I set the WebP quality?
75
- You don't. The plugin will try to detect the quality of the source file, and use same quality as that. You do however have an option to select a maximum quality - usefull, because there is seldom any need for a quality above 85 on ordinary web content. In case quality of the source file cannot be determined (that feature requires that Imagick or GraphicsMagick is installed), it will be set to 80.
76
 
77
  ### How do I make this work with a CDN?
78
  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.
@@ -82,28 +84,22 @@ The plugin takes care of setting the "Vary" HTTP header to "Accept" when routing
82
  ### How do I donate?
83
  Putting this question in the "frequently" asked questions section is of course some mixture of humour, sarcasm and wishful thinking. In case there really is someone out there wanting to donate, you can simply write to me, and we can arrange. My contact information is available here https://www.bitwise-it.dk/contact. I have paypal and of course an ordinary bank account.
84
 
85
- ## Changes in 0.5.0
86
- This version works on many more setups than the previous. Also uses less resources and handles when images are changed.
87
-
88
- * Configuration is now stored in a separate configuration file instead of storing directly in the *.htaccess* file and passing it on via query string. When updating, these settings are migrated automatically.
89
- * Handles setups where Wordpress has been given its own directory (both methods mentioned [here](https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory))
90
- * Handles setups where *wp-content* has been moved, even out of Wordpress root.
91
- * Handles setups where Uploads folder has been moved, even out of *wp-content*.
92
- * Handles setups where Plugins folder has been moved, even out of *wp-content* or out of Wordpress root
93
- * Is not as likely to be subject to firewalls blocking requests (in 0.4.0, we passed all options in a querystring, and that could trigger firewalls under some circumstances)
94
- * Is not as likely to be subject to rewrite rules from other plugins interfering. WebP Express now stores the .htaccess in the wp-content folder (if you allow it). As this is deeper than the root folder, the rules in here takes precedence over rules in the main *.htaccess*
95
- * The *.htaccess* now passes the complete absulute path to the source file instead of a relative path. This is a less error-prone method.
96
- * Reconverts the webp, if source image has changed
97
- * Now runs on version 1.0.0 of [WebP On Demand](https://github.com/rosell-dk/webp-on-demand). Previously ran on 0.3.0
98
- * Now takes care of only loading the PHP classes when needed in order not to slow down your Wordpress. The frontend will only need to process four lines of code. The backend footprint is also quite small now (80 lines of code of hooks)
99
- * Now works in Wordpress 4.0 - 4.6.
100
- * Added cache-breaking tokens to image test links
101
- * Denies deactivation if rewrite rules could not be removed
102
- * Refactored thoroughly
103
- * More helpful texts.
104
- * Extensive testing. Tested on Wordpress 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8 and 4.9. Tested with PHP 5.6, PHP 7.0 and PHP 7.1. Tested on Apache and LiteSpeed. Tested when missing various write permissions. Tested migration. Tested when installed in root, in subfolder, when Wordpress has its own directory (both methods), when wp-content is moved out of Wordpress directory, when plugins is moved out of Wordpress directory, when both of them are moved and when uploads have been moved.
105
-
106
- For more info, see the closed issues on the 0.5.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/milestone/2?closed=1
107
 
108
  # Roadmap
109
 
2
 
3
  Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library, galleries, theme images etc).
4
 
5
+ The plugin is available on the Wordpress codex ([here](https://wordpress.org/plugins/webp-express/)), and developed on github ([here](https://github.com/rosell-dk/webp-express/)).
6
+
7
  ## Description
8
 
9
  This plugin let's you take advantage of the WebP image format with only a little effort. Install, configure, test, forget - and enjoy the increased performance of your website.
10
 
11
  The plugin basically routes jpeg/png images to an image converter, or - if the image converter has already converted the image - directly to a converted image. The approach has the benefit that is works regardless of how an image found its way into your server - be it Media Library, Galleries, or even theme images referenced with CSS.
12
 
13
+ The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and its "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md)
14
 
15
  #### Benefits
16
+ - Much faster load time for images in blink based browsers such as Chrome and Opera (accounting for ~73% of all traffic, according to [caniuse.com](https://caniuse.com/webp)). The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
17
  - Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
18
  - Better ranking in Google searches (performance is taken into account by Google)
19
  - Less bandwidth consumption - makes a difference when abroad and in the parts of the world with slow and expensive internet connections.
49
  - Reload the page
50
  - Find a jpeg or png image in the list. In the "type" column, it should say "webp"
51
 
52
+ In order to test that the image is not being reconverted every time, look at the Response headers of the image. There should be a "X-WebP-Convert-Status" header. It should say "Serving existing converted image" the first time, but "Serving existing converted image" on subsequent requests (WebP-Express is based upon [WebP Convert](https://github.com/rosell-dk/webp-convert)).
53
 
54
+ You can also append `?debug` after any image url, in order to run a conversion, and see the conversion report. Also, if you append `?reconvert` after an image url, you will force a reconversion of the image.
55
 
56
  ### Notes
57
 
58
  *Note:*
59
+ The redirect rules created in *.htaccess* are pointing to a PHP script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
60
 
61
  *Note:*
62
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
63
 
64
  *Note:*
65
+ The plugin has not been tested in multisite configurations. It's on the roadmap...
66
 
67
 
68
  ## Limitations
73
 
74
  ## Frequently Asked Questions
75
 
76
+ ### Why do I not see the option to set WebP quality to auto?
77
+ 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*
78
 
79
  ### How do I make this work with a CDN?
80
  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.
84
  ### How do I donate?
85
  Putting this question in the "frequently" asked questions section is of course some mixture of humour, sarcasm and wishful thinking. In case there really is someone out there wanting to donate, you can simply write to me, and we can arrange. My contact information is available here https://www.bitwise-it.dk/contact. I have paypal and of course an ordinary bank account.
86
 
87
+ ## Changes in 0.6.0
88
+ This version added option for setting caching header, fixed a serious issue with *Imagick*, added a new converter, *Gmagick*, added a great deal of options to *Cwebp* and generally improved the interface.
89
+
90
+ * Added option for caching
91
+ * Fixed long standing and serious issue with Imagick converter. It no longer generates webp images in poor quality
92
+ * Added gmagick as a new conversion method
93
+ * WebPExpress now runs on newly released WebP-Convert 1.2.0
94
+ * Added many new options for *cwebp*
95
+ * You can now quickly see converter status by hovering over a converter
96
+ * You can now choose between having quality auto-detected or not (if the server supports detecting quality).
97
+ * If the server does not support detecting quality, the WPC converter will display a quality "auto" option
98
+ * Added special intro message for those who has no working conversion methods
99
+ * Added help texts for options
100
+ * Settings are now saved, when changing converter options. Too many times, I found myself forgetting to save...
101
+
102
+ For more info, see the closed issues on the 0.6.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.6.0
 
 
 
 
 
 
103
 
104
  # Roadmap
105
 
README.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://www.bitwise-it.dk/contact
4
  Tags: webp, images, performance
5
  Requires at least: 4.0
6
  Tested up to: 4.9
7
- Stable tag: 0.5.0
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -17,10 +17,10 @@ This plugin let's you take advantage of the WebP image format with only a little
17
 
18
  The plugin basically routes jpeg/png images to an image converter, or - if the image converter has already converted the image - directly to a converted image. The approach has the benefit that is works regardless of how an image found its way into your server - be it Media Library, Galleries, or even theme images referenced with CSS.
19
 
20
- The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and some of the ecosystem around it: [WebPConvertAndServe](https://github.com/rosell-dk/webp-convert-and-serve) and [WebPOnDemand](https://github.com/rosell-dk/webp-on-demand)
21
 
22
  #### Benefits
23
- - Much faster load time on images in Chrome browsers. The converted images are typically less than half the size (for jpeg), while maintaining the same quality. Bear in mind that images typically are responsible for most of the bandwidth usage.
24
  - Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
25
  - Better ranking in Google searches (performance is taken into account by Google)
26
  - Less bandwidth consumption - makes a difference when abroad and in the parts of the world with slow and expensive internet connections.
@@ -57,21 +57,21 @@ Note that the plugin does not change any HTML. In the HTML the image src is stil
57
  - Reload the page
58
  - Find a jpeg or png image in the list. In the "type" column, it should say "webp"
59
 
60
- In order to test that the image is not being reconverted every time, look at the Response headers of the image. There should be a "X-WebP-On-Demand" header. It should say "Converting image (handed over to WebPConvertAndServe)" the first time, but "Serving existing converted image" on subsequent requests (WebP-Express is based upon [WebP On Demand](https://github.com/rosell-dk/webp-on-demand)). When routed to image converter, there should also be some headers beginning with "X-WebP-Convert-And-Serve", which reveals information about the conversion.
61
 
62
  You can also append ?debug after any image url, in order to run a conversion, and see the conversion report.
63
- Btw: If you append ?reconvert after an image url, you will force a reconversion of the image.
64
 
65
  ### Notes
66
 
67
  *Note:*
68
- The redirect rules created in *.htaccess* are pointing to the WebP on demand script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
69
 
70
  *Note:*
71
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
72
 
73
  *Note:*
74
- The plugin has not been tested in multisite configurations. It's on the roadmap!
75
 
76
  == Limitations ==
77
 
@@ -81,8 +81,8 @@ The plugin has not been tested in multisite configurations. It's on the roadmap!
81
 
82
  == Frequently Asked Questions ==
83
 
84
- = How do I set the WebP quality? =
85
- You don't. The plugin will try to detect the quality of the source file, and use same quality as that. You do however have an option to select a maximum quality - usefull, because there is seldom any need for a quality above 85 on ordinary web content. In case quality of the source file cannot be determined (that feature requires that Imagick or GraphicsMagick is installed), it will be set to 80.
86
 
87
  = How do I make this work with a CDN? =
88
  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.
@@ -100,33 +100,27 @@ Putting this question in the "frequently" asked questions section is of course s
100
 
101
  == Changelog ==
102
 
103
- = 0.5.0 =
104
- This version works on many more setups than the previous. Also uses less resources and handles when images are changed.
105
-
106
- * Configuration is now stored in a separate configuration file instead of storing directly in the *.htaccess* file and passing it on via query string. When updating, these settings are migrated automatically.
107
- * Handles setups where Wordpress has been given its own directory (both methods mentioned [here](https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory))
108
- * Handles setups where *wp-content* has been moved, even out of Wordpress root.
109
- * Handles setups where Uploads folder has been moved, even out of *wp-content*.
110
- * Handles setups where Plugins folder has been moved, even out of *wp-content* or out of Wordpress root
111
- * Is not as likely to be subject to firewalls blocking requests (in 0.4.0, we passed all options in a querystring, and that could trigger firewalls under some circumstances)
112
- * Is not as likely to be subject to rewrite rules from other plugins interfering. WebP Express now stores the .htaccess in the wp-content folder (if you allow it). As this is deeper than the root folder, the rules in here takes precedence over rules in the main *.htaccess*
113
- * The *.htaccess* now passes the complete absulute path to the source file instead of a relative path. This is a less error-prone method.
114
- * Reconverts the webp, if source image has changed
115
- * Now runs on version 1.0.0 of [WebP On Demand](https://github.com/rosell-dk/webp-on-demand). Previously ran on 0.3.0
116
- * Now takes care of only loading the PHP classes when needed in order not to slow down your Wordpress. The frontend will only need to process four lines of code. The backend footprint is also quite small now (80 lines of code of hooks)
117
- * Now works in Wordpress 4.0 - 4.6.
118
- * Added cache-breaking tokens to image test links
119
- * Denies deactivation if rewrite rules could not be removed
120
- * Refactored thoroughly
121
- * More helpful texts.
122
- * Extensive testing. Tested on Wordpress 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8 and 4.9. Tested with PHP 5.6, PHP 7.0 and PHP 7.1. Tested on Apache and LiteSpeed. Tested when missing various write permissions. Tested migration. Tested when installed in root, in subfolder, when Wordpress has its own directory (both methods), when wp-content is moved out of Wordpress directory, when plugins is moved out of Wordpress directory, when both of them are moved and when uploads have been moved.
123
-
124
- For more info, see the closed issues on the 0.5.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/milestone/2?closed=1
125
 
126
  == Upgrade Notice ==
127
 
128
- = 0.5.0 =
129
- This version is a leap forward regarding stability. Also uses less resources and handles when images are changed.
130
 
131
  == Roadmap ==
132
 
4
  Tags: webp, images, performance
5
  Requires at least: 4.0
6
  Tested up to: 4.9
7
+ Stable tag: 0.6.0
8
  Requires PHP: 5.6
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
17
 
18
  The plugin basically routes jpeg/png images to an image converter, or - if the image converter has already converted the image - directly to a converted image. The approach has the benefit that is works regardless of how an image found its way into your server - be it Media Library, Galleries, or even theme images referenced with CSS.
19
 
20
+ The plugin builds on [WebPConvert](https://github.com/rosell-dk/webp-convert) and its "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/webp-on-demand/webp-on-demand.md)
21
 
22
  #### Benefits
23
+ - Much faster load time for images in blink based browsers such as Chrome and Opera (accounting for ~73% of all traffic, according to [caniuse.com](https://caniuse.com/webp)). The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
24
  - Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
25
  - Better ranking in Google searches (performance is taken into account by Google)
26
  - Less bandwidth consumption - makes a difference when abroad and in the parts of the world with slow and expensive internet connections.
57
  - Reload the page
58
  - Find a jpeg or png image in the list. In the "type" column, it should say "webp"
59
 
60
+ In order to test that the image is not being reconverted every time, look at the Response headers of the image. There should be a "X-WebP-Convert-Status" header. It should say "Serving existing converted image" the first time, but "Serving existing converted image" on subsequent requests (WebP-Express is based upon [WebP Convert](https://github.com/rosell-dk/webp-convert)).
61
 
62
  You can also append ?debug after any image url, in order to run a conversion, and see the conversion report.
63
+ Also, you append ?reconvert after an image url, you will force a reconversion of the image.
64
 
65
  ### Notes
66
 
67
  *Note:*
68
+ The redirect rules created in *.htaccess* are pointing to a PHP script. If you happen to change the url path of your plugins, the rules will have to be updated. The *.htaccess* also passes the path to wp-content (relative to document root) to the script, so the script knows where to find its configuration and where to store converted images. So again, if you move the wp-content folder, or perhaps moves Wordpress to a subfolder, the rules will have to be updated. As moving these things around is is a rare situation, WebP Express are not using any resources monitoring this. However, it will do the check when you visit the settings page.
69
 
70
  *Note:*
71
  Do not simply remove the plugin without deactivating it first. Deactivation takes care of removing the rules in the *.htaccess* file. With the rules there, but converter gone, your Google Chrome visitors will not see any jpeg images.
72
 
73
  *Note:*
74
+ The plugin has not been tested in multisite configurations. It's on the roadmap...
75
 
76
  == Limitations ==
77
 
81
 
82
  == Frequently Asked Questions ==
83
 
84
+ = Why do I not see the option to set WebP quality to auto? =
85
+ 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*
86
 
87
  = How do I make this work with a CDN? =
88
  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.
100
 
101
  == Changelog ==
102
 
103
+ = 0.6.0 =
104
+ This version added option for setting caching header, fixed a serious issue with *Imagick*, added a new converter, *Gmagick*, added a great deal of options to *Cwebp* and generally improved the interface.
105
+
106
+ * Added option for caching
107
+ * Fixed long standing and serious issue with Imagick converter. It no longer generates webp images in poor quality
108
+ * Added gmagick as a new conversion method
109
+ * WebPExpress now runs on newly released WebP-Convert 1.2.0
110
+ * Added many new options for *cwebp*
111
+ * You can now quickly see converter status by hovering over a converter
112
+ * You can now choose between having quality auto-detected or not (if the server supports detecting quality).
113
+ * If the server does not support detecting quality, the WPC converter will display a quality "auto" option
114
+ * Added special intro message for those who has no working conversion methods
115
+ * Added help texts for options
116
+ * Settings are now saved, when changing converter options. Too many times, I found myself forgetting to save...
117
+
118
+ For more info, see the closed issues on the 0.6.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/issues?q=is%3Aclosed+milestone%3A0.6.0
 
 
 
 
 
 
119
 
120
  == Upgrade Notice ==
121
 
122
+ = 0.6.0 =
123
+ Adding caching option, fixed imagick converter, added gmagick converter and more!
124
 
125
  == Roadmap ==
126
 
changelog.txt CHANGED
@@ -1,3 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 0.4.0 =
2
  This version fixes some misbehaviours and provides new http headers with info about the conversion process.
3
 
@@ -7,6 +30,7 @@ This version fixes some misbehaviours and provides new http headers with info ab
7
  * WebPExpress options are now removed when plugin is uninstalled.
8
  * No longer generates .htaccess rules on install. The user now has to actively go to Web Express setting and save first
9
  * Added a "first time" message on options page and a reactivation message
 
10
  For more info, see the closed issues on the github repository: https://github.com/rosell-dk/webp-express/milestone/1?closed=1
11
 
12
  = 0.3.1 =
1
+ = 0.5.0 =
2
+ This version works on many more setups than the previous. Also uses less resources and handles when images are changed.
3
+
4
+ * Configuration is now stored in a separate configuration file instead of storing directly in the *.htaccess* file and passing it on via query string. When updating, these settings are migrated automatically.
5
+ * Handles setups where Wordpress has been given its own directory (both methods mentioned [here](https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory))
6
+ * Handles setups where *wp-content* has been moved, even out of Wordpress root.
7
+ * Handles setups where Uploads folder has been moved, even out of *wp-content*.
8
+ * Handles setups where Plugins folder has been moved, even out of *wp-content* or out of Wordpress root
9
+ * Is not as likely to be subject to firewalls blocking requests (in 0.4.0, we passed all options in a querystring, and that could trigger firewalls under some circumstances)
10
+ * Is not as likely to be subject to rewrite rules from other plugins interfering. WebP Express now stores the .htaccess in the wp-content folder (if you allow it). As this is deeper than the root folder, the rules in here takes precedence over rules in the main *.htaccess*
11
+ * The *.htaccess* now passes the complete absulute path to the source file instead of a relative path. This is a less error-prone method.
12
+ * Reconverts the webp, if source image has changed
13
+ * Now runs on version 1.0.0 of [WebP On Demand](https://github.com/rosell-dk/webp-on-demand). Previously ran on 0.3.0
14
+ * Now takes care of only loading the PHP classes when needed in order not to slow down your Wordpress. The frontend will only need to process four lines of code. The backend footprint is also quite small now (80 lines of code of hooks)
15
+ * Now works in Wordpress 4.0 - 4.6.
16
+ * Added cache-breaking tokens to image test links
17
+ * Denies deactivation if rewrite rules could not be removed
18
+ * Refactored thoroughly
19
+ * More helpful texts.
20
+ * Extensive testing. Tested on Wordpress 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8 and 4.9. Tested with PHP 5.6, PHP 7.0 and PHP 7.1. Tested on Apache and LiteSpeed. Tested when missing various write permissions. Tested migration. Tested when installed in root, in subfolder, when Wordpress has its own directory (both methods), when wp-content is moved out of Wordpress directory, when plugins is moved out of Wordpress directory, when both of them are moved and when uploads have been moved.
21
+
22
+ For more info, see the closed issues on the 0.5.0 milestone on our github repository: https://github.com/rosell-dk/webp-express/milestone/2?closed=1
23
+
24
  = 0.4.0 =
25
  This version fixes some misbehaviours and provides new http headers with info about the conversion process.
26
 
30
  * WebPExpress options are now removed when plugin is uninstalled.
31
  * No longer generates .htaccess rules on install. The user now has to actively go to Web Express setting and save first
32
  * Added a "first time" message on options page and a reactivation message
33
+
34
  For more info, see the closed issues on the github repository: https://github.com/rosell-dk/webp-express/milestone/1?closed=1
35
 
36
  = 0.3.1 =
composer.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "minimum-stability": "dev",
3
+ "repositories": [
4
+ {
5
+ "type": "vcs",
6
+ "url": "https://github.com/rosell-dk/webp-convert"
7
+ }
8
+ ],
9
+ "require": {
10
+ "rosell-dk/webp-convert": "dev-master"
11
+ }
12
+ }
lib/activate-first-time.php CHANGED
@@ -9,6 +9,9 @@ use \WebPExpress\Config;
9
  include_once __DIR__ . '/classes/Messenger.php';
10
  use \WebPExpress\Messenger;
11
 
 
 
 
12
  include_once __DIR__ . '/classes/PlatformInfo.php';
13
  use \WebPExpress\PlatformInfo;
14
 
@@ -61,5 +64,5 @@ if (PlatformInfo::isApache() || PlatformInfo::isLiteSpeed()) {
61
  Messenger::addMessage(
62
  'info',
63
  'WebP Express was installed successfully. To start using it, you must ' .
64
- '<a href="options-general.php?page=webp_express_settings_page">configure it here</a>.'
65
  );
9
  include_once __DIR__ . '/classes/Messenger.php';
10
  use \WebPExpress\Messenger;
11
 
12
+ include_once __DIR__ . '/classes/Paths.php';
13
+ use \WebPExpress\Paths;
14
+
15
  include_once __DIR__ . '/classes/PlatformInfo.php';
16
  use \WebPExpress\PlatformInfo;
17
 
64
  Messenger::addMessage(
65
  'info',
66
  'WebP Express was installed successfully. To start using it, you must ' .
67
+ '<a href="' . Paths::getSettingsUrl() . '">configure it here</a>.'
68
  );
lib/admin.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
 
3
  // When an update requires a migration, the number should be increased
4
- define('WEBPEXPRESS_MIGRATION_VERSION', '1');
5
 
6
  if (WEBPEXPRESS_MIGRATION_VERSION != get_option('webp-express-migration-version', 0)) {
7
  // run migration logic
1
  <?php
2
 
3
  // When an update requires a migration, the number should be increased
4
+ define('WEBPEXPRESS_MIGRATION_VERSION', '2');
5
 
6
  if (WEBPEXPRESS_MIGRATION_VERSION != get_option('webp-express-migration-version', 0)) {
7
  // run migration logic
lib/classes/Config.php CHANGED
@@ -90,18 +90,62 @@ class Config
90
  $options = $config;
91
  $options['converters'] = [];
92
  foreach ($config['converters'] as $converter) {
93
- if (isset($converter['deactivated'])) continue;
94
 
95
  $options['converters'][] = $converter;
96
  }
97
  foreach ($options['converters'] as &$c) {
 
 
 
 
 
 
 
 
98
  unset ($c['id']);
 
 
99
  if (!isset($c['options'])) {
100
  $c = $c['converter'];
101
  }
102
  }
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  unset($options['image-types']);
 
 
 
105
  return $options;
106
  }
107
 
90
  $options = $config;
91
  $options['converters'] = [];
92
  foreach ($config['converters'] as $converter) {
93
+ if (isset($converter['deactivated']) && ($converter['deactivated'])) continue;
94
 
95
  $options['converters'][] = $converter;
96
  }
97
  foreach ($options['converters'] as &$c) {
98
+ if ($c['converter'] == 'cwebp') {
99
+ if (isset($c['options']['set-size']) && $c['options']['set-size']) {
100
+ unset($c['options']['set-size']);
101
+ } else {
102
+ unset($c['options']['set-size']);
103
+ unset($c['options']['size-in-percentage']);
104
+ }
105
+ }
106
  unset ($c['id']);
107
+ unset($c['working']);
108
+ unset($c['error']);
109
  if (!isset($c['options'])) {
110
  $c = $c['converter'];
111
  }
112
  }
113
 
114
+ if (isset($options['cache-control'])) {
115
+ $cacheControl = $options['cache-control'];
116
+ $cacheControlOptions = [
117
+ 'no-header' => '',
118
+ 'one-second' => 'public, max-age=1',
119
+ 'one-minute' => 'public, max-age=60',
120
+ 'one-hour' => 'public, max-age=3600',
121
+ 'one-day' => 'public, max-age=86400',
122
+ 'one-week' => 'public, max-age=604800',
123
+ 'one-month' => 'public, max-age=2592000',
124
+ 'one-year' => 'public, max-age=31536000',
125
+ ];
126
+
127
+ if (isset($cacheControlOptions[$cacheControl])) {
128
+ $options['cache-control-header'] = $cacheControlOptions[$cacheControl];
129
+ } else {
130
+ $options['cache-control-header'] = $options['cache-control-custom'];
131
+ }
132
+ }
133
+
134
+ $auto = (isset($options['quality-auto']) && $options['quality-auto']);
135
+ $qualitySpecific = (isset($options['quality-specific']) ? $options['quality-specific'] : 70);
136
+ if ($auto) {
137
+ $options['quality'] = 'auto';
138
+ } else {
139
+ $options['quality'] = $qualitySpecific;
140
+ unset ($options['max-quality']);
141
+ }
142
+ unset($options['quality-auto']);
143
+ unset($options['quality-specific']);
144
+
145
  unset($options['image-types']);
146
+ unset($options['cache-control']);
147
+ unset($options['cache-control-custom']);
148
+
149
  return $options;
150
  }
151
 
lib/classes/ConvertersHelper.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class ConvertersHelper
6
+ {
7
+ public static $defaultConverters = [
8
+ ['converter' => 'gd', 'options' => ['skip-pngs' => true]],
9
+ ['converter' => 'cwebp', 'options' => [
10
+ 'use-nice' => false,
11
+ 'try-common-system-paths' => true,
12
+ 'try-supplied-binary-for-os' => true,
13
+ 'method' => 6,
14
+ 'size-in-percentage' => 45,
15
+ 'low-memory' => false,
16
+ 'command-line-options' => '-low_memory',
17
+ ]],
18
+ ['converter' => 'imagick'],
19
+ ['converter' => 'gmagick'],
20
+ ['converter' => 'wpc', 'options' => ['quality' => 'auto']],
21
+ ['converter' => 'ewww'],
22
+ ];
23
+
24
+ public static function getDefaultConverterNames()
25
+ {
26
+ return array_column(self::$defaultConverters, 'converter');
27
+ }
28
+
29
+ public static function getConverterNames($converters)
30
+ {
31
+ return array_column(self::normalize($converters), 'converter');
32
+ }
33
+
34
+ public static function normalize($converters)
35
+ {
36
+ foreach ($converters as &$converter) {
37
+ if (!isset($converter['converter'])) {
38
+ $converter = ['converter' => $converter];
39
+ }
40
+ if (!isset($converter['options'])) {
41
+ $converter['options'] = [];
42
+ }
43
+ }
44
+ return $converters;
45
+ }
46
+
47
+ /**
48
+ * Those converters in second, but not in first will be appended to first
49
+ */
50
+ public static function mergeConverters($first, $second)
51
+ {
52
+ $namesInFirst = self::getConverterNames($first);
53
+ $second = self::normalize($second);
54
+
55
+ foreach ($second as $converter) {
56
+ if (!in_array($converter['converter'], $namesInFirst)) {
57
+ $first[] = $converter;
58
+ }
59
+ }
60
+ return $first;
61
+ }
62
+
63
+ }
lib/classes/Paths.php CHANGED
@@ -308,4 +308,18 @@ APACHE
308
  ];
309
  }
310
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  }
308
  ];
309
  }
310
 
311
+ /* Get complete url to admin (no trailing slash) */
312
+ public static function getAdminUrl()
313
+ {
314
+ if (!function_exists('get_admin_url')) {
315
+ require_once ABSPATH . 'wp-includes/link-template.php';
316
+ }
317
+ return untrailingslashit(get_admin_url());
318
+ }
319
+
320
+ public static function getSettingsUrl()
321
+ {
322
+ return self::getAdminUrl() . '/' . 'options-general.php?page=webp_express_settings_page';
323
+ }
324
+
325
  }
lib/classes/TestRun.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "Config.php";
6
+ use \WebPExpress\Config;
7
+
8
+ include_once __DIR__ . '/../classes/ConvertersHelper.php';
9
+ use \WebPExpress\ConvertersHelper;
10
+
11
+ include_once "Paths.php";
12
+ use \WebPExpress\Paths;
13
+
14
+ include_once "FileHelper.php";
15
+ use \WebPExpress\FileHelper;
16
+
17
+ include_once __DIR__ . '/../../vendor/autoload.php';
18
+ use \WebPConvert\Converters\ConverterHelper;
19
+
20
+ /**
21
+ *
22
+ */
23
+
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 = [];
41
+ $errors = [];
42
+
43
+ // We need wod options.
44
+ // But we cannot simply use loadWodOptions - because that would leave out the deactivated
45
+ // converters. And we need to test all converters - even the deactivated ones.
46
+ // So we load config, set "deactivated" to false, and generate Wod options from the config
47
+ $config = Config::loadConfig();
48
+ if ((!$config) || (!isset($config['converters'])) || (count($config['converters']) == 0)) {
49
+ $config = [
50
+ 'converters' => ConvertersHelper::$defaultConverters
51
+ ];
52
+ } else {
53
+ // set deactivated to false on all converters
54
+ foreach($config['converters'] as &$converter) {
55
+ $converter['deactivated'] = false;
56
+ }
57
+
58
+ // merge missing converters in
59
+ $config['converters'] = ConvertersHelper::mergeConverters($config['converters'], ConvertersHelper::$defaultConverters);
60
+ // echo '<pre>' . print_r($config, true) . '</pre>';
61
+
62
+ }
63
+
64
+ $options = Config::generateWodOptionsFromConfigObj($config);
65
+ $options['converters'] = ConvertersHelper::normalize($options['converters']);
66
+
67
+ //echo '<pre>' . print_r($options, true) . '</pre>';
68
+ foreach ($options['converters'] as $converter) {
69
+ $converterId = $converter['converter'];
70
+ try {
71
+ $converterOptions = array_merge($options, $converter['options']);
72
+ unset($converterOptions['converters']);
73
+
74
+ ConverterHelper::runConverter($converterId, $source, $destination, $converterOptions);
75
+ $workingConverters[] = $converterId;
76
+ } catch (\Exception $e) {
77
+ //echo $e->getMessage() . '<br>';
78
+ $errors[$converterId] = $e->getMessage();
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
+ }
lib/migrate/migrate.php CHANGED
@@ -36,12 +36,9 @@ if (!(State::getState('configured', false))) {
36
  include __DIR__ . '/migrate1.php';
37
  }
38
 
39
- // When a new version needs a new migration, uncomment this:
40
- // (make sure to grab the option again - it might have been changed in the migration above)
41
- /*
42
  if (intval(get_option('webp-express-migration-version', 0)) == 1) {
43
  // run migration 2
44
  include __DIR__ . '/migrate2.php';
45
  }
46
- */
47
  }
36
  include __DIR__ . '/migrate1.php';
37
  }
38
 
39
+ // We make sure to grab the option again - it might have been changed in the migration above
 
 
40
  if (intval(get_option('webp-express-migration-version', 0)) == 1) {
41
  // run migration 2
42
  include __DIR__ . '/migrate2.php';
43
  }
 
44
  }
lib/migrate/migrate1.php CHANGED
@@ -111,7 +111,7 @@ function webpexpress_migrate1_migrateOptions()
111
  if ($mainResult != 'failed') {
112
  Messenger::addMessage(
113
  'success',
114
- 'WebP Express has successfully migrated its configuration and updated the rewrite rules'
115
  );
116
  } else {
117
  Messenger::addMessage(
@@ -119,7 +119,7 @@ function webpexpress_migrate1_migrateOptions()
119
  'WebP Express has successfully migrated its configuration.' .
120
  'However, WebP Express could not update the rewrite rules<br>' .
121
  'You need to change some permissions. Head to the ' .
122
- '<a href="options-general.php?page=webp_express_settings_page">settings page</a> ' .
123
  'and try to save the settings there (it will provide more information about the problem)'
124
  );
125
  }
111
  if ($mainResult != 'failed') {
112
  Messenger::addMessage(
113
  'success',
114
+ 'WebP Express has successfully migrated its configuration and updated the rewrite rules to 0.5.0'
115
  );
116
  } else {
117
  Messenger::addMessage(
119
  'WebP Express has successfully migrated its configuration.' .
120
  'However, WebP Express could not update the rewrite rules<br>' .
121
  'You need to change some permissions. Head to the ' .
122
+ '<a href="' . Paths::getSettingsUrl() . '">settings page</a> ' .
123
  'and try to save the settings there (it will provide more information about the problem)'
124
  );
125
  }
lib/migrate/migrate2.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once __DIR__ . '/../classes/Paths.php';
6
+ use \WebPExpress\Paths;
7
+
8
+ include_once __DIR__ . '/../classes/Messenger.php';
9
+ use \WebPExpress\Messenger;
10
+
11
+ include_once __DIR__ . '/../classes/TestRun.php';
12
+ use \WebPExpress\TestRun;
13
+
14
+ /* helper. Remove dir recursively. No warnings - fails silently
15
+ Set $removeTheDirItself to false if you want to empty the dir
16
+ */
17
+ function webpexpress_migrate2_rrmdir($dir, $removeTheDirItself = true) {
18
+ if (@is_dir($dir)) {
19
+ $objects = @scandir($dir);
20
+ foreach ($objects as $object) {
21
+ if ($object != "." && $object != "..") {
22
+ $file = $dir . "/" . $object;
23
+ if (@is_dir($file)) {
24
+ webpexpress_migrate2_rrmdir($file);
25
+ } else {
26
+ @unlink($file);
27
+ }
28
+ }
29
+ }
30
+ if ($removeTheDirItself) {
31
+ @rmdir($dir);
32
+ }
33
+ }
34
+ }
35
+
36
+ $testResult = TestRun::getConverterStatus();
37
+ if ($testResult) {
38
+ $workingConverters = $testResult['workingConverters'];
39
+ if (in_array('imagick', $workingConverters)) {
40
+ webpexpress_migrate2_rrmdir(Paths::getCacheDirAbs(), false);
41
+ Messenger::addMessage(
42
+ 'info',
43
+ 'WebP Express has emptied the image cache. In previous versions, the imagick converter ' .
44
+ 'was generating images in poor quality. This has been fixed. As your system meets the ' .
45
+ 'requirements of the imagick converter, it might be that you have been using that. So ' .
46
+ 'to be absolutely sure you do not have inferior conversions in the cache dir, it has been emptied.'
47
+ );
48
+ }
49
+ if (in_array('gmagick', $workingConverters)) {
50
+ Messenger::addMessage(
51
+ 'info',
52
+ 'Good news! WebP Express is now able to use the gmagick extension for conversion - ' .
53
+ 'and your server meets the requirements!'
54
+ );
55
+ }
56
+ if (in_array('cwebp', $workingConverters)) {
57
+ Messenger::addMessage(
58
+ 'info',
59
+ 'WebP Express added several options for the cwebp conversion method. ' .
60
+ '<a href="' . Paths::getSettingsUrl() . '">Go to the settings page to check it out</a>.'
61
+ );
62
+ }
63
+ }
64
+ Messenger::addMessage(
65
+ 'info',
66
+ 'WebP Express can now be configured to cache the webp images. You might want to ' .
67
+ '<a href="' . Paths::getSettingsUrl() . '">do that</a>.'
68
+ );
69
+
70
+
71
+ update_option('webp-express-migration-version', '2');
lib/options/css/webp-express-options-page.css CHANGED
@@ -19,45 +19,74 @@
19
  padding: 0;
20
  }*/
21
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  #converters li {
24
  cursor: move;
25
- border: 1px solid #e5e5e5;
26
  box-shadow: 0 1px 1px rgba(0,0,0,.04);
27
  line-height: 1.4em;
28
- background-color: #fafafa;
29
- padding: 10px;
30
- max-width: 400px;
 
 
 
31
  }
32
  #converters li:hover {
 
 
 
 
 
 
 
 
33
  border-color: #999;
34
- box-shadow: 0 1px 2px rgba(0,0,0,.1);
35
  }
36
  #converters li.deactivated,
37
  #converters li.deactivated a.configure-converter,
38
- #converters li.deactivated a.test-converter {
39
- color: #aaa;
 
 
 
 
 
 
 
 
40
  }
41
  #converters li a {
42
  cursor: pointer;
43
  }
44
-
45
-
46
-
47
-
48
 
49
  #converters li > * {
50
  vertical-align: middle;
51
 
52
  }
53
- #converters li > span {
54
  display: inline-block;
55
  line-height: 1;
56
  }
57
- #converters li > span.text {
58
- font-size: 15px;
59
  padding-left: 10px;
60
- width: 165px;
61
  }
62
 
63
  #converters li > a.btn {
@@ -75,8 +104,61 @@
75
  background-color: #eee;*/
76
  }
77
  #converters li > a.configure-converter {
 
 
 
 
 
 
 
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
 
 
80
  }
81
 
82
  /*
@@ -90,9 +172,35 @@
90
  display: inline-block;
91
  }
92
 
 
 
 
 
93
  .converter-options .info {
94
  margin-bottom: 20px;
95
  }
96
  .converter-options button {
97
  margin-top: 15px;
98
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  padding: 0;
20
  }*/
21
 
22
+ @media (min-width: 782px) {
23
+ #webpexpress_settings .form-table th {
24
+ padding-top: 5px;
25
+ padding-bottom: 5px;
26
+ width: 180px;
27
+ }
28
+ #webpexpress_settings .form-table td {
29
+ padding-top: 1px;
30
+ padding-bottom: 5px;
31
+ }
32
+
33
+ }
34
 
35
  #converters li {
36
  cursor: move;
37
+ border: 1px solid grey;
38
  box-shadow: 0 1px 1px rgba(0,0,0,.04);
39
  line-height: 1.4em;
40
+ background-color: #fff;
41
+ padding: 10px 10px 0;
42
+ max-width: 465px;
43
+ min-height: 30px;
44
+ position: relative;
45
+ font-size: 12px;
46
  }
47
  #converters li:hover {
48
+ border-color: #000;
49
+ box-shadow: 0 1px 2px rgba(0,0,0,.2);
50
+ }
51
+ #converters li.deactivated {
52
+ border-color: #ccc;
53
+ background-color: #f0f0f0;
54
+ }
55
+ #converters li.deactivated:hover {
56
  border-color: #999;
 
57
  }
58
  #converters li.deactivated,
59
  #converters li.deactivated a.configure-converter,
60
+ #converters li.deactivated a.test-converter,
61
+ #converters li.deactivated a.activate-converter {
62
+ color: #888;
63
+ }
64
+ #converters li.not-operational {
65
+ border-style: dotted;
66
+ border-color: #666;
67
+ }
68
+ #converters li.not-operational:hover {
69
+ border-style: solid;
70
  }
71
  #converters li a {
72
  cursor: pointer;
73
  }
74
+ #converters li[data-id='gmagick'] a.configure-converter,
75
+ #converters li[data-id='imagick'] a.configure-converter {
76
+ visibility: hidden;
77
+ }
78
 
79
  #converters li > * {
80
  vertical-align: middle;
81
 
82
  }
83
+ #converters li > div {
84
  display: inline-block;
85
  line-height: 1;
86
  }
87
+ #converters li > .text {
 
88
  padding-left: 10px;
89
+ width: 205px;
90
  }
91
 
92
  #converters li > a.btn {
104
  background-color: #eee;*/
105
  }
106
  #converters li > a.configure-converter {
107
+ }
108
+ #converters li .status {
109
+ font-size: 10px;
110
+ position: absolute;
111
+ right: 14px;
112
+ bottom: 2px;
113
+ }
114
 
115
+ #converters li .status svg {
116
+ padding: 5px;
117
+ }
118
+ #converters li svg#status_ok {
119
+ color: #008000;
120
+ }
121
+ #converters li.deactivated svg#status_ok {
122
+ color: #99cc99;
123
+ }
124
+ #converters li svg#status_not_ok {
125
+ color: #b11010; /* 444444 */
126
+ }
127
+ #converters li.deactivated svg#status_not_ok {
128
+ color: #999999;
129
+ }
130
+
131
+
132
+ #converters li .popup,
133
+ .help .popup {
134
+ display: none;
135
+ position: absolute;
136
+ border: 1px solid #666;
137
+ z-index:2;
138
+ background-color: #ffffaa;
139
+ padding: 8px 10px;
140
+ /*left: 22px;
141
+ top: 20px;*/
142
+ margin-top: -5px;
143
+ font-size: 12px;
144
+ /*white-space: nowrap;*/
145
+ color: #000;
146
+ min-width: 150px;
147
+ font-weight: normal;
148
+ text-align: left;
149
+ max-width: 450px;
150
+ }
151
+ #converters li.operational .popup {
152
+ background-color: #80ff80;
153
+ }
154
+ /* #converters li .status:hover .popup */
155
+ #converters li:hover .status .popup,
156
+ .help:hover .popup {
157
+ display: block;
158
+ }
159
 
160
+ .help .popup {
161
+ min-width: 250px;
162
  }
163
 
164
  /*
172
  display: inline-block;
173
  }
174
 
175
+ .converter-options.wpc label {
176
+ min-width: 100px;
177
+ }
178
+
179
  .converter-options .info {
180
  margin-bottom: 20px;
181
  }
182
  .converter-options button {
183
  margin-top: 15px;
184
  }
185
+ .converter-options div {
186
+ margin-bottom: 15px;
187
+ }
188
+
189
+ .help {
190
+ text-align: center;
191
+ display: inline-block;
192
+ border-radius: 50%;
193
+ background-color: #00a0ee;
194
+ width: 16px;
195
+ height: 16px;
196
+ border: 0px solid #00a0ee;
197
+ color: white;
198
+ font-weight: bolder;
199
+ margin-left: 7px;
200
+ cursor: pointer;
201
+ font-size: 12px;
202
+ line-height: 16px;
203
+ vertical-align: top;
204
+ font-family: sans-serif;
205
+ position: relative;
206
+ }
lib/options/enqueue_scripts.php CHANGED
@@ -10,7 +10,7 @@ wp_register_script(
10
  'webp-express-options-page',
11
  plugins_url('js/webp-express-options-page.js', __FILE__),
12
  ['sortable'],
13
- '0.5.1'
14
  );
15
  wp_enqueue_script('webp-express-options-page');
16
 
@@ -25,7 +25,7 @@ wp_register_style(
25
  'webp-express-options-page-css',
26
  plugins_url('css/webp-express-options-page.css', __FILE__),
27
  null,
28
- '0.5.1'
29
  );
30
  wp_enqueue_style('webp-express-options-page-css');
31
 
10
  'webp-express-options-page',
11
  plugins_url('js/webp-express-options-page.js', __FILE__),
12
  ['sortable'],
13
+ '0.6.0dev9'
14
  );
15
  wp_enqueue_script('webp-express-options-page');
16
 
25
  'webp-express-options-page-css',
26
  plugins_url('css/webp-express-options-page.css', __FILE__),
27
  null,
28
+ '0.6.0dev7'
29
  );
30
  wp_enqueue_style('webp-express-options-page-css');
31
 
lib/options/js/webp-express-options-page.js CHANGED
@@ -5,59 +5,79 @@ window.convertersMap = {};
5
 
6
  window.currentlyEditing = '';
7
 
8
- window.defaultConverters = [
9
- {
10
- 'converter': 'cwebp',
11
- 'options': {
12
- 'use-nice': false
13
- },
14
- },
15
- {
16
- 'converter': 'wpc',
17
- },
18
- {
19
- 'converter': 'gd',
20
- 'options': {
21
- 'skip-pngs': true
22
- },
23
- },
24
- {
25
- 'converter': 'ewww',
26
- },
27
- {
28
- 'converter': 'imagick',
29
- },
30
- ]
31
-
32
  function resetToDefaultConverters() {
33
  window.converters = window.defaultConverters;
34
  }
35
 
36
- function addMissingConverters() {
37
  // check if all available converters are in the array.
38
  // if not - add!
39
  // the double loop could be avoided with map. But arrays are so small, so not worth it
40
  for (var i=0; i<window.defaultConverters.length; i++) {
41
- var checkMe = window.defaultConverters[i]['converter'];
42
  var found = false;
43
  for (var j=0; j<window.converters.length; j++) {
44
- if (window.converters[j]['converter'] == checkMe) {
 
45
  found = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
  }
48
  if (!found) {
49
  window.converters.push(window.defaultConverters[i]);
50
  }
 
 
 
 
 
 
51
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
 
53
  }
 
 
 
 
 
 
 
 
 
 
54
  function generateConverterHTML(converter) {
55
- html = '<li data-id="' + converter['id'] + '" class="' + (converter.deactivated ? 'deactivated' : '') + '">';
56
  //html += '<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>';
57
  // html += '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>';
58
  // html += '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#888888" 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>';
59
- html += '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><path d="M2 13.5h14V12H2v1.5zm0-4h14V8H2v1.5zM2 4v1.5h14V4H2z"/></svg>';
60
- html += '<span class="text">' + converter['id'] + '</span>';
 
 
61
  html += '<a class="configure-converter btn" onclick="configureConverter(\'' + converter['id'] + '\')">configure</a>';
62
  html += '<a class="test-converter btn" onclick="testConverter(\'' + converter['id'] + '\')">test</a>';
63
 
@@ -68,6 +88,26 @@ html += '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox=
68
  html += '<a class="deactivate-converter btn" onclick=deactivateConverter(\'' + converter['id'] + '\')>deactivate</a>';
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  html += '</li>';
72
  return html;
73
  }
@@ -148,11 +188,51 @@ function setConvertersHTML() {
148
  updateInputValue();
149
  }
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  document.addEventListener('DOMContentLoaded', function() {
152
  //resetToDefaultConverters();
153
- addMissingConverters();
 
154
  setConvertersHTML();
 
 
155
 
 
 
 
 
 
 
 
156
 
157
  //alert(sortable.toArray());
158
  });
@@ -175,6 +255,10 @@ function setConverterOption(converter, optionName, value) {
175
  converter['options'][optionName] = value;
176
  }
177
 
 
 
 
 
178
  function configureConverter(id) {
179
  var converter = window.convertersMap[id];
180
  window.currentlyEditing = id;
@@ -189,12 +273,25 @@ function configureConverter(id) {
189
  document.getElementById('wpc_secret').value = getConverterOption(converter, 'secret', '');
190
  document.getElementById('wpc_url_2').value = getConverterOption(converter, 'url-2', '');
191
  document.getElementById('wpc_secret_2').value = getConverterOption(converter, 'secret-2', '');
 
 
 
 
 
 
 
192
  break;
193
  case 'gd':
194
  document.getElementById('gd_skip_pngs').checked = getConverterOption(converter, 'skip-pngs', '');
195
  break;
196
  case 'cwebp':
197
  document.getElementById('cwebp_use_nice').checked = getConverterOption(converter, 'use-nice', '');
 
 
 
 
 
 
198
  break;
199
 
200
  }
@@ -214,16 +311,37 @@ function updateConverterOptions() {
214
  setConverterOption(converter, 'secret', document.getElementById('wpc_secret').value);
215
  setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
216
  setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  break;
218
  case 'gd':
219
  setConverterOption(converter, 'skip-pngs', document.getElementById('gd_skip_pngs').checked);
220
  break;
221
  case 'cwebp':
222
  setConverterOption(converter, 'use-nice', document.getElementById('cwebp_use_nice').checked);
 
 
 
 
 
 
223
  break;
224
  }
225
  updateInputValue();
226
  tb_remove();
 
227
  }
228
 
229
  function testConverter(id) {
5
 
6
  window.currentlyEditing = '';
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  function resetToDefaultConverters() {
9
  window.converters = window.defaultConverters;
10
  }
11
 
12
+ function addMissingConvertersAndOptions() {
13
  // check if all available converters are in the array.
14
  // if not - add!
15
  // the double loop could be avoided with map. But arrays are so small, so not worth it
16
  for (var i=0; i<window.defaultConverters.length; i++) {
17
+ var checkMe = window.defaultConverters[i];
18
  var found = false;
19
  for (var j=0; j<window.converters.length; j++) {
20
+ var checkMe2 = window.converters[j]
21
+ if (checkMe2['converter'] == checkMe['converter']) {
22
  found = true;
23
+
24
+ if (checkMe['options']) {
25
+ for (var optionName in checkMe['options']) {
26
+ if (checkMe['options'].hasOwnProperty(optionName)) {
27
+ if (!checkMe2['options']) {
28
+ checkMe2['options'] = [];
29
+ }
30
+ if (!checkMe2['options'].hasOwnProperty(optionName)) {
31
+ checkMe2['options'][optionName] = checkMe['options'][optionName];
32
+ }
33
+ }
34
+ }
35
+ }
36
  }
37
  }
38
  if (!found) {
39
  window.converters.push(window.defaultConverters[i]);
40
  }
41
+ }
42
+ }
43
+
44
+ function addMissingOptions() {
45
+
46
+ }
47
 
48
+ function getConversionMethodDescription(converterId) {
49
+ var descriptions = {
50
+ 'cwebp': '<i>cwebp</i> binary',
51
+ 'wpc': 'wpc cloud converter',
52
+ 'ewww': 'ewww cloud converter',
53
+ 'gd': 'Gd extension',
54
+ 'imagick': 'Imagick extension',
55
+ 'gmagick': 'Gmagick extension',
56
+ };
57
+ if (descriptions[converterId]) {
58
+ return descriptions[converterId];
59
  }
60
+ return converterId;
61
  }
62
+
63
+ function htmlEscape(str) {
64
+ return str
65
+ .replace(/&/g, '&amp;')
66
+ .replace(/"/g, '&quot;')
67
+ .replace(/'/g, '&#39;')
68
+ .replace(/</g, '&lt;')
69
+ .replace(/>/g, '&gt;');
70
+ }
71
+
72
  function generateConverterHTML(converter) {
73
+ html = '<li data-id="' + converter['id'] + '" class="' + (converter.deactivated ? 'deactivated' : '') + ' ' + (converter.working ? 'operational' : 'not-operational') + '">';
74
  //html += '<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>';
75
  // html += '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>';
76
  // html += '<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 100.000000 100.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)" fill="#888888" 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>';
77
+ html += '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><path d="M2 13.5h14V12H2v1.5zm0-4h14V8H2v1.5zM2 4v1.5h14V4H2z"/></svg>';
78
+ html += '<div class="text">';
79
+ html += getConversionMethodDescription(converter['id']);
80
+ html += '</div>';
81
  html += '<a class="configure-converter btn" onclick="configureConverter(\'' + converter['id'] + '\')">configure</a>';
82
  html += '<a class="test-converter btn" onclick="testConverter(\'' + converter['id'] + '\')">test</a>';
83
 
88
  html += '<a class="deactivate-converter btn" onclick=deactivateConverter(\'' + converter['id'] + '\')>deactivate</a>';
89
  }
90
 
91
+ html += '<div class="status">';
92
+ if (converter.working) {
93
+ html += '<svg id="status_ok" width="21" height="21" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256.000000 256.000000" preserveAspectRatio="xMidYMid meet">';
94
+ html += '<g fill="currentcolor" stroke="none" transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"><path d="M1064 2545 c-406 -72 -744 -324 -927 -690 -96 -193 -127 -333 -127 -575 0 -243 33 -387 133 -585 177 -351 518 -606 907 -676 118 -22 393 -17 511 8 110 24 252 78 356 136 327 183 569 525 628 887 19 122 19 338 0 460 -81 498 -483 914 -990 1025 -101 22 -389 28 -491 10z m814 -745 c39 -27 73 -59 77 -70 9 -27 10 -25 -372 -590 -345 -510 -357 -524 -420 -512 -19 4 -98 74 -250 225 -123 121 -225 228 -228 238 -3 10 1 31 9 47 20 40 125 132 149 132 11 0 79 -59 162 -140 79 -77 146 -140 149 -140 3 0 38 48 78 108 95 143 465 678 496 720 35 46 64 42 150 -18z"/></g></svg>';
95
+ html += '<div class="popup">' + converter['id'] + ' is operational</div>';
96
+ } else {
97
+ // + converter['id'] + ' is not operational<br>';
98
+ //html += 'not operational. ';
99
+ if (converter['error']) {
100
+ html += '<svg id="status_not_ok" width="21" height="21" title="not operational" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500.000000 500.000000" preserveAspectRatio="xMidYMid meet">';
101
+ html += '<g fill="currentcolor" stroke="none" transform="translate(0.000000,500.000000) scale(0.100000,-0.100000)"><path d="M2315 4800 c-479 -35 -928 -217 -1303 -527 -352 -293 -615 -702 -738 -1151 -104 -380 -104 -824 0 -1204 107 -389 302 -724 591 -1013 354 -354 785 -572 1279 -646 196 -30 476 -30 672 0 494 74 925 292 1279 646 354 354 571 784 646 1279 30 197 30 475 0 672 -75 495 -292 925 -646 1279 -289 289 -624 484 -1013 591 -228 62 -528 91 -767 74z m353 -511 c458 -50 874 -272 1170 -624 417 -497 536 -1174 308 -1763 -56 -145 -176 -367 -235 -434 -4 -4 -566 552 -1250 1236 l-1243 1243 94 60 c354 229 754 327 1156 282z m864 -3200 c-67 -59 -289 -179 -434 -235 -946 -366 -2024 172 -2322 1158 -47 155 -66 276 -73 453 -13 362 84 704 290 1023 l60 94 1243 -1243 c684 -684 1240 -1246 1236 -1250z"/></g></svg>';
102
+ html += '<div class="popup">';
103
+
104
+ html += htmlEscape(converter['error']);
105
+
106
+ html += '</div>';
107
+ }
108
+ }
109
+ html += '</div>';
110
+
111
  html += '</li>';
112
  return html;
113
  }
188
  updateInputValue();
189
  }
190
 
191
+ function updateCacheControlCustomVisibility() {
192
+ var cacheControlValue = document.getElementById('cache_control_select').value;
193
+ var customEl = document.getElementById('cache_control_custom');
194
+ if (cacheControlValue == 'custom') {
195
+ customEl.setAttribute('type', 'text');
196
+ } else {
197
+ customEl.setAttribute('type', 'hidden');
198
+ }
199
+ }
200
+
201
+ function updateQualityVisibility() {
202
+ var qualityAutoEl = document.getElementById('quality_auto_select');
203
+ if (!qualityAutoEl) {
204
+ return;
205
+ }
206
+ var qualityAutoValue = qualityAutoEl.value;
207
+ var maxQualityRowEl = document.getElementById('max_quality_row');
208
+ var qualitySpecificRowEl = document.getElementById('quality_specific_row');
209
+
210
+ //alert(qualityAutoValue);
211
+ if (qualityAutoValue == 'auto_on') {
212
+ maxQualityRowEl.style['display'] = 'table-row';
213
+ qualitySpecificRowEl.style['display'] = 'none';
214
+ } else {
215
+ maxQualityRowEl.style['display'] = 'none';
216
+ qualitySpecificRowEl.style['display'] = 'table-row';
217
+ }
218
+
219
+ }
220
+
221
  document.addEventListener('DOMContentLoaded', function() {
222
  //resetToDefaultConverters();
223
+ addMissingConvertersAndOptions();
224
+ addMissingOptions();
225
  setConvertersHTML();
226
+ updateCacheControlCustomVisibility();
227
+ updateQualityVisibility();
228
 
229
+ document.getElementById('cache_control_select').addEventListener('change', function() {
230
+ updateCacheControlCustomVisibility();
231
+ });
232
+
233
+ document.getElementById('quality_auto_select').addEventListener('change', function() {
234
+ updateQualityVisibility();
235
+ });
236
 
237
  //alert(sortable.toArray());
238
  });
255
  converter['options'][optionName] = value;
256
  }
257
 
258
+ function wpcQualityChanged() {
259
+ var q = document.getElementById('wpc_quality').value;
260
+ document.getElementById('wpc_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
261
+ }
262
  function configureConverter(id) {
263
  var converter = window.convertersMap[id];
264
  window.currentlyEditing = id;
273
  document.getElementById('wpc_secret').value = getConverterOption(converter, 'secret', '');
274
  document.getElementById('wpc_url_2').value = getConverterOption(converter, 'url-2', '');
275
  document.getElementById('wpc_secret_2').value = getConverterOption(converter, 'secret-2', '');
276
+ var q = getConverterOption(converter, 'quality', 'not_set');
277
+ if (document.getElementById('wpc_quality')) {
278
+ document.getElementById('wpc_quality').value = q;
279
+ document.getElementById('wpc_max_quality_div').style['display'] = (q == 'auto' ? 'block' : 'none');
280
+ document.getElementById('wpc_max_quality').value = getConverterOption(converter, 'max-quality', 85);
281
+ }
282
+
283
  break;
284
  case 'gd':
285
  document.getElementById('gd_skip_pngs').checked = getConverterOption(converter, 'skip-pngs', '');
286
  break;
287
  case 'cwebp':
288
  document.getElementById('cwebp_use_nice').checked = getConverterOption(converter, 'use-nice', '');
289
+ document.getElementById('cwebp_method').value = getConverterOption(converter, 'method', '');
290
+ document.getElementById('cwebp_try_common_system_paths').checked = getConverterOption(converter, 'try-common-system-paths', '');
291
+ document.getElementById('cwebp_try_supplied_binary').checked = getConverterOption(converter, 'try-supplied-binary-for-os', '');
292
+ document.getElementById('cwebp_set_size').checked = getConverterOption(converter, 'set-size', '');
293
+ document.getElementById('cwebp_size_in_percentage').value = getConverterOption(converter, 'size-in-percentage', '');
294
+ document.getElementById('cwebp_command_line_options').value = getConverterOption(converter, 'command-line-options', '');
295
  break;
296
 
297
  }
311
  setConverterOption(converter, 'secret', document.getElementById('wpc_secret').value);
312
  setConverterOption(converter, 'url-2', document.getElementById('wpc_url_2').value);
313
  setConverterOption(converter, 'secret-2', document.getElementById('wpc_secret_2').value);
314
+
315
+ if (document.getElementById('wpc_quality')) {
316
+ var q = document.getElementById('wpc_quality').value;
317
+ if (q == 'auto') {
318
+ setConverterOption(converter, 'quality', 'auto');
319
+ setConverterOption(converter, 'max-quality', document.getElementById('wpc_max_quality').value);
320
+ } else {
321
+ delete converter['options']['quality'];
322
+ delete converter['options']['max-quality'];
323
+ }
324
+ } else {
325
+ delete converter['options']['quality'];
326
+ delete converter['options']['max-quality'];
327
+ }
328
  break;
329
  case 'gd':
330
  setConverterOption(converter, 'skip-pngs', document.getElementById('gd_skip_pngs').checked);
331
  break;
332
  case 'cwebp':
333
  setConverterOption(converter, 'use-nice', document.getElementById('cwebp_use_nice').checked);
334
+ setConverterOption(converter, 'method', document.getElementById('cwebp_method').value);
335
+ setConverterOption(converter, 'try-common-system-paths', document.getElementById('cwebp_try_common_system_paths').checked);
336
+ setConverterOption(converter, 'try-supplied-binary-for-os', document.getElementById('cwebp_try_supplied_binary').checked);
337
+ setConverterOption(converter, 'set-size', document.getElementById('cwebp_set_size').checked);
338
+ setConverterOption(converter, 'size-in-percentage', document.getElementById('cwebp_size_in_percentage').value);
339
+ setConverterOption(converter, 'command-line-options', document.getElementById('cwebp_command_line_options').value);
340
  break;
341
  }
342
  updateInputValue();
343
  tb_remove();
344
+ document.getElementById('webpexpress_settings').submit();
345
  }
346
 
347
  function testConverter(id) {
lib/options/options-hooks.php CHANGED
@@ -17,7 +17,7 @@ add_action('admin_post_webpexpress_settings_submit', function() {
17
  });
18
 
19
  global $pagenow;
20
- if (($pagenow == 'options-general.php') && ($_GET['page'] == 'webp_express_settings_page')) {
21
  add_action('admin_enqueue_scripts', function () {
22
  include __DIR__ . '/enqueue_scripts.php';
23
  });
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
  });
lib/options/page-messages.php CHANGED
@@ -8,6 +8,8 @@ use \WebPExpress\Messenger;
8
  use \WebPExpress\PlatformInfo;
9
  use \WebPExpress\FileHelper;
10
 
 
 
11
  if ((!State::getState('configured', false))) {
12
  include __DIR__ . "/page-welcome.php";
13
  }
8
  use \WebPExpress\PlatformInfo;
9
  use \WebPExpress\FileHelper;
10
 
11
+ //include __DIR__ . "/page-welcome.php";
12
+
13
  if ((!State::getState('configured', false))) {
14
  include __DIR__ . "/page-welcome.php";
15
  }
lib/options/page-welcome.php CHANGED
@@ -5,7 +5,7 @@ use \WebPExpress\Config;
5
  use \WebPExpress\State;
6
  use \WebPExpress\Messenger;
7
  use \WebPExpress\PlatformInfo;
8
- use \WebPExpress\FileHelper;
9
 
10
  $indexDir = Paths::getIndexDirAbs();
11
  $homeDir = Paths::getHomeDirAbs();
@@ -13,12 +13,38 @@ $wpContentDir = Paths::getWPContentDirAbs();
13
  $pluginDir = Paths::getPluginDirAbs();
14
  $uploadDir = Paths::getUploadDirAbs();
15
 
16
- echo '<div style="background-color: #cfc; padding: 20px; border: 1px solid #ccc; color: black">';
 
 
 
 
 
 
17
  echo '<h3>Welcome!</h3>';
18
- echo '<p>The rewrite rules are not active yet. They will be activated the first time you click the "Save settings" button.</p>';
19
- echo '<p>Before you do that, I suggest you find out which converters that works. Start from the top. Click "test" next to a converter to test it. Try also clicking the "configure" buttons</p>';
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
 
22
  if (Paths::isWPContentDirMovedOutOfAbsPath()) {
23
  if (!Paths::canWriteHTAccessRulesHere($wpContentDir)) {
24
  echo '<p><b>Oh, one more thing</b>. Unless you are going to put the rewrite rules into your configuration manually, ';
@@ -104,7 +130,7 @@ if (Paths::isWPContentDirMovedOutOfAbsPath()) {
104
  }
105
  }
106
  }
107
-
108
  /*
109
  if(Paths::canWriteHTAccessRulesHere($wpContentDir)) {
110
 
5
  use \WebPExpress\State;
6
  use \WebPExpress\Messenger;
7
  use \WebPExpress\PlatformInfo;
8
+
9
 
10
  $indexDir = Paths::getIndexDirAbs();
11
  $homeDir = Paths::getHomeDirAbs();
13
  $pluginDir = Paths::getPluginDirAbs();
14
  $uploadDir = Paths::getUploadDirAbs();
15
 
16
+ $weKnowThereAreNoWorkingConverters = false;
17
+ if ($testResult !== false) {
18
+ $workingConverters = $testResult['workingConverters'];
19
+ $weKnowThereAreNoWorkingConverters = (count($workingConverters) == 0);
20
+ }
21
+ $bgColor = ($weKnowThereAreNoWorkingConverters ? '#fff' : '#cfc');
22
+ echo '<div style="background-color: ' . $bgColor . '; padding: 10px 20px; border: 1px solid #ccc; color: black; margin-top:15px">';
23
  echo '<h3>Welcome!</h3>';
 
 
24
 
25
+ //if ($localQualityDetectionWorking) {
26
+ //echo 'Local quality detection working :)';
27
+ //}
28
+
29
+ if ($weKnowThereAreNoWorkingConverters) {
30
+ echo '<p>Unfortunately none of the local conversion methods are available on your server. ' .
31
+ 'but do not despear! - You have options!' .
32
+ '<ol style="list-style-position:outside">' .
33
+ '<li>You can purchase a key for the <a target="_blank" href="https://ewww.io/plans/">ewww cloud converter</a>. They do not charge credits for webp conversions, so all you ever have to pay is the one dollar start-up fee :)</li>' .
34
+ '<li>You can set up a <a target="_blank" href="https://github.com/rosell-dk/webp-convert-cloud-service">webp-convert-cloud-service</a> on another server and connect to that. Its open source.</li>' .
35
+ '<li>You can try to meet the server requirements of cwebp, gd, imagick or gmagick. Check out <a target="_blank" href="https://github.com/rosell-dk/webp-convert/wiki/Meeting-the-requirements-of-the-converters">this wiki page</a> on how to do that</li>' .
36
+ '</ol></p>' .
37
+ "<p>Btw, don't worry, your images still works. The rewrite rules will not be saved until you click the " .
38
+ '"Save settings" button (and you also have "Response on failure" set to "Original image", so they will work even if you click save)</p>';
39
+ } else {
40
+ echo '<p>The rewrite rules are not active yet. They will be activated the first time you click the "Save settings" button.</p>';
41
+ }
42
+ //echo 'working converters:';
43
+ //print_r($workingConverters);
44
+
45
+ //echo '<p>Before you do that, I suggest you find out which converters that works. Start from the top. Click "test" next to a converter to test it. Try also clicking the "configure" buttons</p>';
46
 
47
+ /*
48
  if (Paths::isWPContentDirMovedOutOfAbsPath()) {
49
  if (!Paths::canWriteHTAccessRulesHere($wpContentDir)) {
50
  echo '<p><b>Oh, one more thing</b>. Unless you are going to put the rewrite rules into your configuration manually, ';
130
  }
131
  }
132
  }
133
+ */
134
  /*
135
  if(Paths::canWriteHTAccessRulesHere($wpContentDir)) {
136
 
lib/options/page.php CHANGED
@@ -3,6 +3,9 @@
3
  include_once __DIR__ . '/../classes/Config.php';
4
  use \WebPExpress\Config;
5
 
 
 
 
6
  include_once __DIR__ . '/../classes/FileHelper.php';
7
  use \WebPExpress\FileHelper;
8
 
@@ -21,6 +24,8 @@ use \WebPExpress\PlatformInfo;
21
  include_once __DIR__ . '/../classes/State.php';
22
  use \WebPExpress\State;
23
 
 
 
24
 
25
  if (!current_user_can('manage_options')) {
26
  wp_die('You do not have sufficient permissions to access this page.');
@@ -30,6 +35,25 @@ if (!current_user_can('manage_options')) {
30
  <h2>WebP Express Settings</h2>
31
 
32
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  include __DIR__ . "/page-messages.php";
34
 
35
  /*
@@ -40,38 +64,132 @@ foreach (Paths::getHTAccessDirs() as $dir) {
40
 
41
 
42
  $defaultConfig = [
43
- 'image-types' => 1,
 
 
44
  'fail' => 'original',
 
 
 
45
  'max-quality' => 80,
46
- 'converters' => [],
47
- 'forward-query-string' => true
48
  ];
49
 
 
 
 
50
  $config = Config::loadConfig();
 
51
  if (!$config) {
52
  $config = [];
53
  }
 
54
 
55
  $config = array_merge($defaultConfig, $config);
56
  if ($config['converters'] == null) {
57
  $config['converters'] = [];
58
  }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  // Generate a custom nonce value.
61
  $webpexpress_settings_nonce = wp_create_nonce('webpexpress_settings_nonce');
 
 
 
 
 
62
 
63
- echo '<form action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" method="post" id="webpexpress_settings" >';
 
64
  ?>
65
  <input type="hidden" name="action" value="webpexpress_settings_submit">
66
  <input type="hidden" name="webpexpress_settings_nonce" value="<?php echo $webpexpress_settings_nonce ?>" />
67
 
68
  <?php
69
 
 
 
 
70
  echo '<table class="form-table"><tbody>';
71
 
72
  // Image types
73
  // ------------
74
- echo '<tr><th scope="row">Image types to convert</th><td>';
 
 
75
 
76
  // bitmask
77
  // 1: JPEGs
@@ -89,9 +207,115 @@ echo '</select>';
89
 
90
  echo '</td></tr>';
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  // Response on failure
93
  // --------------------
94
- echo '<tr><th scope="row">Response on failure</th><td>';
 
 
95
 
96
  //$fail = get_option('webp_express_failure_response');
97
  $fail = $config['fail'];
@@ -104,18 +328,6 @@ echo '</select>';
104
  echo '</td></tr>';
105
  // 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>';
106
 
107
- // Max quality
108
- // --------------------
109
- //$maxQuality = get_option('webp_express_max_quality');
110
- $maxQuality = $config['max-quality'];
111
-
112
- echo '<tr><th scope="row">Max quality (0-100)</th><td>';
113
- echo '<input type="text" name="max-quality" value="' . $maxQuality . '">';
114
- echo '</td></tr>';
115
- // 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>';
116
-
117
- // method
118
- //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>';
119
 
120
  echo '</tbody></table>';
121
 
@@ -124,6 +336,8 @@ echo '</tbody></table>';
124
 
125
  $converters = $config['converters'];
126
  echo '<script>window.converters = ' . json_encode($converters) . '</script>';
 
 
127
  echo "<input type='text' name='converters' value='' style='visibility:hidden' />";
128
 
129
  // https://premium.wpmudev.org/blog/handling-form-submissions/
@@ -157,10 +371,13 @@ http://php.net/manual/en/function.set-include-path.php
157
  //echo 'All se bools: ' . print_r($output6, true) . '. Return code:' . $returnCode5;
158
  */
159
 
160
- echo '<h2>Converters</h2>';
161
  $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>';
162
 
163
- echo '<p><i>Drag to reorder. The converter on top will be used. Should it fail, the next will be used, etc</i></p>';
 
 
 
164
  // https://github.com/RubaXa/Sortable
165
 
166
  // Empty list of converters. The list will be populated by the javascript
@@ -168,60 +385,86 @@ echo '<ul id="converters"></ul>';
168
  ?>
169
  <div id="cwebp" style="display:none;">
170
  <div class="cwebp converter-options">
171
- <h3>cwebp</h3>
172
- <div class="info">
173
- cwebp works by executing the cwebp binary from Google. This should normally be your first choice.
174
- Its best in terms of quality, speed and options.
175
- The only catch is that it requires the exec function to be enabled, and that the webserver user is
176
- allowed to execute the cwebp binary (either at known system locations, or one of the precompiled binaries,
177
- that comes with this library).
178
- If you are on a shared host that doesn't allow that, the second best choice would probably be the wpc cloud converter.
179
- </div>
180
  <h3>cweb options</h3>
181
  <div>
182
  <label for="cwebp_use_nice">Use nice</label>
183
  <input type="checkbox" id="cwebp_use_nice">
184
- <br>Enabling "use nice" saves system resources at the cost of slightly slower conversion
185
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  </div>
187
  <br>
188
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
189
  <!-- <a href="javascript: tb_remove();">close</a> -->
190
  </div>
191
  </div>
192
  <div id="gd" style="display:none;">
193
- <div class="ewww converter-options">
194
- <h3>Gd</h3>
195
- <p>
196
- The gd converter uses the Gd extension to do the conversion. It is per default placed below the cloud converters for two reasons.
197
- Firstly, it does not seem to produce quite as good quality as cwebp.
198
- Secondly, it provides no conversion options, besides quality.
199
- The Gd extension is pretty common, so the main feature of this converter is that it <i>may</i> work out of the box.
200
- This is in contrast to the cloud converters, which requires that the user does some setup.
201
- </p>
202
  <h3>Gd options</h3>
203
- <div class="info">
204
- Gd neither supports copying metadata nor exposes any WebP options. Lacking the option to set lossless encoding results in poor encoding of PNGs - the filesize is generally much larger than the original. For this reason, the converter defaults to skip PNG's.
205
- </div>
206
  <div>
207
  <label for="gd_skip_pngs">Skip PNGs</label>
208
  <input type="checkbox" id="gd_skip_pngs">
 
 
 
209
  </div>
210
  <br>
211
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
212
  <!-- <a href="javascript: tb_remove();">close</a> -->
213
  </div>
214
  </div>
215
  <div id="imagick" style="display:none;">
216
  <div class="imagick converter-options">
217
- <h3>Imagick</h3>
218
- <p>
219
- imagick would be your last choice. For some reason it produces conversions that are only marginally better than the originals.
220
- See <a href="https://github.com/rosell-dk/webp-convert/issues/43" target="_blank">this issue</a>. But it is fast.
221
- </p>
222
  <h3>Imagick options</h3>
223
  <div class="info">
224
- imagick has no extra options.
225
  </div>
226
  <br>
227
  <!--
@@ -234,14 +477,11 @@ echo '<ul id="converters"></ul>';
234
  <div class="ewww converter-options">
235
  <h3>Ewww</h3>
236
  <p>
237
- <a href="https://ewww.io/" target="_blank">ewww</a> is a cloud service.
238
- It is a decent alternative for those who don't have the technical know-how to install wpc.
239
- ewww is using cwebp to do the conversion, so quality is great.
240
- ewww however only provides one conversion option (quality), and it does not support "auto"
241
- quality (yet - I have requested the feature and the maintainer are considering it).
242
- Also, it is not free. But very cheap. Like in almost free.
243
  </p>
244
- <h3>Ewww options</h3>
245
  <div>
246
  <label for="ewww_key">Key</label>
247
  <input type="text" id="ewww_key" placeholder="Your API key here">
@@ -253,24 +493,18 @@ echo '<ul id="converters"></ul>';
253
  <input type="text" id="ewww_key_2" placeholder="In case the first one expires...">
254
  </div>
255
  <br>
256
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
257
  <!-- <a href="javascript: tb_remove();">close</a> -->
258
  </div>
259
  </div>
260
  <div id="wpc" style="display:none;">
261
  <div class="wpc converter-options">
262
- <h3>WebPConvert Cloud Service</h3>
263
- wpc is an open source cloud converter based on <a href="https://github.com/rosell-dk/webp-convert" target="_blank">WebPConvert</a>
264
- (this plugin also happens to be based on WebPConvert).
265
- Conversions will of course be slower than cwebp, as images need to go back and forth to the cloud converter.
266
- As images usually just needs to be converted once, the slower conversion
267
- speed is probably acceptable. The conversion quality and options of wpc matches cwebp.
268
- The only catch is that you will need to install the WPC library on a server (or have someone do it for you).
269
  <a href="https://github.com/rosell-dk/webp-convert-cloud-service" target="blank">Visit WPC on github</a>.
270
- If this is a problem, we suggest you turn to ewww.
271
- (PS: A Wordpress plugin is planned, making it easier to set up a WPC instance. Or perhaps the functionality will even be part of this plugin)
272
-
273
- <h3>Options for WebPConvert Cloud Service</h3>
274
  <div>
275
  <label for="wpc_url">URL</label>
276
  <input type="text" id="wpc_url" placeholder="Url to your WPC instance">
@@ -280,7 +514,30 @@ echo '<ul id="converters"></ul>';
280
  <label for="wpc_secret">Secret</label>
281
  <input type="text" id="wpc_secret" placeholder="Secret (must match secret on server side)">
282
  </div>
283
- <br>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  <h4>Fallback (optional)</h4>
285
  <p>In case the first is down, the fallback will be used.</p>
286
  <div>
@@ -292,13 +549,13 @@ echo '<ul id="converters"></ul>';
292
  <input type="text" id="wpc_secret_2" placeholder="Secret (must match secret on server side)">
293
  </div>
294
  <br>
295
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
296
  </div>
297
  </div>
298
 
299
  <table>
300
  <tr>
301
- <td style="padding-right:20px"><?php submit_button('Save settings'); ?></td>
302
  <td><?php submit_button('Save settings and force new .htaccess rules', 'secondary', 'force'); ?></td>
303
  </tr>
304
  </table>
3
  include_once __DIR__ . '/../classes/Config.php';
4
  use \WebPExpress\Config;
5
 
6
+ include_once __DIR__ . '/../classes/ConvertersHelper.php';
7
+ use \WebPExpress\ConvertersHelper;
8
+
9
  include_once __DIR__ . '/../classes/FileHelper.php';
10
  use \WebPExpress\FileHelper;
11
 
24
  include_once __DIR__ . '/../classes/State.php';
25
  use \WebPExpress\State;
26
 
27
+ include_once __DIR__ . '/../classes/TestRun.php';
28
+ use \WebPExpress\TestRun;
29
 
30
  if (!current_user_can('manage_options')) {
31
  wp_die('You do not have sufficient permissions to access this page.');
35
  <h2>WebP Express Settings</h2>
36
 
37
  <?php
38
+
39
+ //update_option('webp-express-migration-version', '1');
40
+
41
+ // Test converters
42
+ $testResult = TestRun::getConverterStatus();
43
+ $workingConverters = [];
44
+ if ($testResult) {
45
+ $workingConverters = $testResult['workingConverters'];
46
+ //print_r($testResult);
47
+ } else {
48
+ Messenger::printMessage(
49
+ 'error',
50
+ 'WebP Express cannot save a test conversion, because it does not have write ' .
51
+ 'access to your upload folder, nor your wp-content folder. Please provide!'
52
+ );
53
+ }
54
+
55
+ $canDetectQuality = TestRun::isLocalQualityDetectionWorking();
56
+
57
  include __DIR__ . "/page-messages.php";
58
 
59
  /*
64
 
65
 
66
  $defaultConfig = [
67
+ 'cache-control' => 'no-header',
68
+ 'cache-control-custom' => 'public, max-age:3600',
69
+ 'converters' => [],
70
  'fail' => 'original',
71
+ 'forward-query-string' => true,
72
+ 'image-types' => 1,
73
+ 'quality-auto' => $canDetectQuality,
74
  'max-quality' => 80,
75
+ 'quality-specific' => 70,
76
+ 'metadata' => 'none',
77
  ];
78
 
79
+ $defaultConverters = ConvertersHelper::$defaultConverters;
80
+
81
+
82
  $config = Config::loadConfig();
83
+ //echo '<pre>' . print_r($config, true) . '</pre>';
84
  if (!$config) {
85
  $config = [];
86
  }
87
+ //$config = [];
88
 
89
  $config = array_merge($defaultConfig, $config);
90
  if ($config['converters'] == null) {
91
  $config['converters'] = [];
92
  }
93
 
94
+ if (count($config['converters']) == 0) {
95
+ // This is first time visit!
96
+
97
+ if (count($workingConverters) == 0) {
98
+ // No converters are working
99
+ // Send ewww converter to top
100
+ $resultPart1 = [];
101
+ $resultPart2 = [];
102
+ foreach ($defaultConverters as $converter) {
103
+ $converterId = $converter['converter'];
104
+ if ($converterId == 'ewww') {
105
+ $resultPart1[] = $converter;
106
+ } else {
107
+ $resultPart2[] = $converter;
108
+ }
109
+ }
110
+ $config['converters'] = array_merge($resultPart1, $resultPart2);
111
+ } else {
112
+ // Send converters not working to the bottom
113
+ // - and also deactivate them..
114
+ $resultPart1 = [];
115
+ $resultPart2 = [];
116
+ foreach ($defaultConverters as $converter) {
117
+ $converterId = $converter['converter'];
118
+ if (in_array($converterId, $workingConverters)) {
119
+ $resultPart1[] = $converter;
120
+ } else {
121
+ $converter['deactivated'] = true;
122
+ $resultPart2[] = $converter;
123
+ }
124
+ }
125
+ $config['converters'] = array_merge($resultPart1, $resultPart2);
126
+ }
127
+
128
+ // $workingConverters
129
+ //echo '<pre>' . print_r($converters, true) . '</pre>';
130
+ } else {
131
+ // not first time visit...
132
+ // merge missing converters in
133
+ $config['converters'] = ConvertersHelper::mergeConverters($config['converters'], ConvertersHelper::$defaultConverters);
134
+ }
135
+
136
+
137
+ // Set "working" and "error" properties
138
+ if ($testResult) {
139
+ foreach ($config['converters'] as &$converter) {
140
+ $converterId = $converter['converter'];
141
+ $hasError = isset($testResult['errors'][$converterId]);
142
+ $working = !$hasError;
143
+ if (isset($converter['working']) && ($converter['working'] != $working)) {
144
+ if ($working) {
145
+ Messenger::printMessage(
146
+ 'info',
147
+ 'Hurray! - The <i>' . $converterId . '</i> conversion method is working now!'
148
+ );
149
+ } else {
150
+ Messenger::printMessage(
151
+ 'warning',
152
+ 'Sad news. The <i>' . $converterId . '</i> conversion method is not working anymore. What happened?'
153
+ );
154
+ }
155
+ }
156
+ $converter['working'] = $working;
157
+ if ($hasError) {
158
+ $converter['error'] = $testResult['errors'][$converterId];
159
+ } else {
160
+ unset($converter['error']);
161
+ }
162
+ }
163
+ }
164
+ //echo '<pre>' . print_r($config['converters'], true) . '</pre>';
165
+
166
+ //echo 'Working converters:' . print_r($workingConverters, true) . '<br>';
167
  // Generate a custom nonce value.
168
  $webpexpress_settings_nonce = wp_create_nonce('webpexpress_settings_nonce');
169
+ ?>
170
+ <p>
171
+ <i>WebP Express takes care of serving autogenerated WebP images instead of jpeg/png to browsers that supports WebP.<br>
172
+ The settings below does not affect your original images - only the converted webp images, and the redirection rules.</i>
173
+ </p>
174
 
175
+ <?php
176
+ echo '<form id="webpexpress_settings" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" method="post" >';
177
  ?>
178
  <input type="hidden" name="action" value="webpexpress_settings_submit">
179
  <input type="hidden" name="webpexpress_settings_nonce" value="<?php echo $webpexpress_settings_nonce ?>" />
180
 
181
  <?php
182
 
183
+ function helpIcon($text) {
184
+ return '<div class="help">?<div class="popup">' . $text . '</div></div>';
185
+ }
186
  echo '<table class="form-table"><tbody>';
187
 
188
  // Image types
189
  // ------------
190
+ echo '<tr><th scope="row">Image types to convert';
191
+ 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>');
192
+ echo '</th><td>';
193
 
194
  // bitmask
195
  // 1: JPEGs
207
 
208
  echo '</td></tr>';
209
 
210
+ // Quality
211
+ // --------------------
212
+
213
+ if ($canDetectQuality) {
214
+ echo '<tr><th scope="row">Quality';
215
+ echo helpIcon('If "Auto" is selected, the converted image will get same quality as source. Auto is recommended!');
216
+ echo '</th><td>';
217
+ $qualityAuto = $config['quality-auto'];;
218
+ echo '<select id="quality_auto_select" name="quality-auto">';
219
+ echo '<option value="auto_on"' . ($qualityAuto ? ' selected' : '') . '>Auto</option>';
220
+ echo '<option value="auto_off"' . (!$qualityAuto ? ' selected' : '') . '>Specific value</option>';
221
+ echo '</select>';
222
+
223
+ echo '</td></tr>';
224
+
225
+
226
+ // Max quality
227
+ // --------------------
228
+ $maxQuality = $config['max-quality'];
229
+
230
+ echo '<tr id="max_quality_row"><th scope="row">Max quality (0-100)';
231
+ echo helpIcon('Quality is expensive byte-wise. For most websites, more than 80 is a waste of bytes. ' .
232
+ 'This option allows you to limit the quality to whatever is lowest: ' .
233
+ 'the quality of the source or max quality. Recommended value: Somewhere between 50-85');
234
+ echo '</th><td>';
235
+
236
+ echo '<input type="text" size=3 name="max-quality" value="' . $maxQuality . '">';
237
+ echo '</td></tr>';
238
+ } else {
239
+
240
+ }
241
+
242
+ // Quality - specific
243
+ // --------------------
244
+ $qualitySpecific = $config['quality-specific'];
245
+
246
+ echo '<tr id="quality_specific_row"><th scope="row">Quality (0-100)';
247
+ if ($canDetectQuality) {
248
+ echo helpIcon('All converted images will be encoded with this quality');
249
+ } else {
250
+ echo helpIcon('All converted images will be encoded with this quality. ' .
251
+ 'For the wpc converter, you will however have the option to use override this, and use ' .
252
+ '"auto". If you install imagick or gmagick, quality can have "auto" for all convertion methods. '
253
+ );
254
+ }
255
+ echo '</th><td>';
256
+
257
+ echo '<input type="text" size=3 name="quality-specific" value="' . $qualitySpecific . '">';
258
+ echo '</td></tr>';
259
+
260
+
261
+
262
+
263
+ // 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>';
264
+
265
+ // method
266
+ //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>';
267
+
268
+ // Cache-Control
269
+ // --------------------
270
+ //$maxQuality = get_option('webp_express_max_quality');
271
+ $cacheControl = $config['cache-control'];
272
+ $cacheControlCustom = $config['cache-control-custom'];
273
+
274
+ echo '<tr><th scope="row">Caching';
275
+ echo helpIcon(
276
+ 'Controls the cache-control header for the converted image. ' .
277
+ 'This header is only sent when a converted image is successfully delivered (either existing, or new ' .
278
+ 'conversion). In case of failure, headers will be sent to prevent caching.');
279
+ echo '</th><td>';
280
+ echo '<select id="cache_control_select" name="cache-control">';
281
+ echo '<option value="no-header"' . ($cacheControl == 'no-header' ? ' selected' : '') . '>Do not set Cache-Control header</option>';
282
+ echo '<option value="one-second"' . ($cacheControl == 'one-second' ? ' selected' : '') . '>One second</option>';
283
+ echo '<option value="one-minute"' . ($cacheControl == 'one-minute' ? ' selected' : '') . '>One minute</option>';
284
+ echo '<option value="one-hour"' . ($cacheControl == 'one-hour' ? ' selected' : '') . '>One hour</option>';
285
+ echo '<option value="one-day"' . ($cacheControl == 'one-day' ? ' selected' : '') . '>One day</option>';
286
+ echo '<option value="one-week"' . ($cacheControl == 'one-week' ? ' selected' : '') . '>One week</option>';
287
+ echo '<option value="one-month"' . ($cacheControl == 'one-month' ? ' selected' : '') . '>One month</option>';
288
+ echo '<option value="one-year"' . ($cacheControl == 'one-year' ? ' selected' : '') . '>One year</option>';
289
+ echo '<option value="custom"' . ($cacheControl == 'custom' ? ' selected' : '') . '>Custom Cache-Control header</option>';
290
+ echo '</select><br>';
291
+ echo '<input type="text" id="cache_control_custom" name="cache-control-custom" value="' . $cacheControlCustom . '">';
292
+ echo '</td></tr>';
293
+
294
+
295
+ // Metadata
296
+ // --------------------
297
+ //$maxQuality = get_option('webp_express_max_quality');
298
+ $metadata = $config['metadata'];
299
+
300
+ echo '<tr><th scope="row">Metadata';
301
+ 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');
302
+ echo '</th><td>';
303
+
304
+ echo '<select name="metadata">';
305
+ echo '<option value="none"' . ($metadata == 'none' ? ' selected' : '') . '>No metadata in webp</option>';
306
+ echo '<option value="all"' . ($metadata == 'all' ? ' selected' : '') . '>Copy all metadata to webp</option>';
307
+ echo '</select>';
308
+ echo '</td></tr>';
309
+ // 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>';
310
+
311
+ // method
312
+ //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>';
313
+
314
  // Response on failure
315
  // --------------------
316
+ echo '<tr><th scope="row">Response on failure';
317
+ echo helpIcon('Determines what to serve in case the image conversion should fail.');
318
+ echo '</th><td>';
319
 
320
  //$fail = get_option('webp_express_failure_response');
321
  $fail = $config['fail'];
328
  echo '</td></tr>';
329
  // 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>';
330
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
  echo '</tbody></table>';
333
 
336
 
337
  $converters = $config['converters'];
338
  echo '<script>window.converters = ' . json_encode($converters) . '</script>';
339
+ echo '<script>window.defaultConverters = ' . json_encode($defaultConverters) . '</script>';
340
+
341
  echo "<input type='text' name='converters' value='' style='visibility:hidden' />";
342
 
343
  // https://premium.wpmudev.org/blog/handling-form-submissions/
371
  //echo 'All se bools: ' . print_r($output6, true) . '. Return code:' . $returnCode5;
372
  */
373
 
374
+ echo '<h2>Conversion methods to try</h2>';
375
  $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>';
376
 
377
+ echo '<p><i>Drag to reorder. The conversion method on top will first be tried. ';
378
+ echo 'Should it fail, the next will be used, etc.<br>';
379
+ echo 'To learn more about the conversion methods, ';
380
+ echo '<a target="_blank" href="https://github.com/rosell-dk/webp-convert/blob/master/docs/converters.md">Go here</a></i></p>';
381
  // https://github.com/RubaXa/Sortable
382
 
383
  // Empty list of converters. The list will be populated by the javascript
385
  ?>
386
  <div id="cwebp" style="display:none;">
387
  <div class="cwebp converter-options">
 
 
 
 
 
 
 
 
 
388
  <h3>cweb options</h3>
389
  <div>
390
  <label for="cwebp_use_nice">Use nice</label>
391
  <input type="checkbox" id="cwebp_use_nice">
392
+ <br>Enabling this option saves system resources at the cost of slightly slower conversion
393
+ </div>
394
+ <div>
395
+ <label for="cwebp_try_common_system_paths">Try to execute cweb binary at common locations</label>
396
+ <input type="checkbox" id="cwebp_try_common_system_paths">
397
+ <br>If checked, we will look for binaries in common locations, such as <i>/usr/bin/cwebp</i>
398
+ </div>
399
+ <div>
400
+ <label for="cwebp_try_common_system_paths">Try precompiled cwebp</label>
401
+ <input type="checkbox" id="cwebp_try_supplied_binary">
402
+ <br>This plugin ships with precompiled cweb binaries for different platforms. If checked, and we have a precompiled binary for your OS, we will try to exectute it
403
+ </div>
404
+ <div>
405
+ <label for="cwebp_method">Method (0-6)</label>
406
+ <input type="text" size="2" id="cwebp_method">
407
+ <br>This parameter controls the trade off between encoding speed and the compressed file size and quality.
408
+ Possible values range from 0 to 6. 0 is fastest. 6 results in best quality.
409
+ </div>
410
+ <div>
411
+ <label for="cwebp_set_size">Set size option (and ignore quality option)</label>
412
+ <input type="checkbox" id="cwebp_set_size">
413
+ <br>This option activates the size option below.
414
+ <?php
415
+ if ($canDetectQuality) {
416
+ echo 'As you have quality detection working on your server, it is probably best to use that, rather ';
417
+ echo 'than the "size" option. Using the size option takes more ressources (it takes about 2.5 times ';
418
+ echo 'longer for cwebp to do a a conversion with the size option than the quality option). Long ';
419
+ echo 'story short, you should probably <i>not</i> activate the size option.';
420
+ } else {
421
+ echo 'As you do not have quality detection working on your server, it is probably a good ';
422
+ echo 'idea to use the size option to avoid making conversions with a higher quality setting ';
423
+ echo 'than the source image. ';
424
+ echo 'Beware, though, that cwebp takes about 2.5 times longer to do a a conversion with the size option set.';
425
+ }
426
+ ?>
427
+ </div>
428
+ <div>
429
+ <label for="cwebp_size_in_percentage">Size (in percentage of source)</label>
430
+ <input type="text" size="2" id="cwebp_size_in_percentage">
431
+ <br>Set the cwebp should aim for, in percentage of the original.
432
+ Usually cwebp can reduce to ~45% of original without loosing quality.
433
+ </div>
434
+ <div>
435
+ <label for="cwebp_command_line_options">Extra command line options</label><br>
436
+ <input type="text" size="40" id="cwebp_command_line_options" style="width:100%">
437
+ <br>This allows you to set any parameter available for cwebp in the same way as
438
+ you would do when executing <i>cwebp</i>. As a syntax example, you could ie. set it to
439
+ "-low_memory -af -f 50 -sharpness 0 -mt -crop 10 10 40 40" (do not include the quotes).
440
+ Read more about all the available parameters in
441
+ <a target="_blank" href="https://developers.google.com/speed/webp/docs/cwebp">the docs</a>
442
  </div>
443
  <br>
444
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update and save settings</button>
445
  <!-- <a href="javascript: tb_remove();">close</a> -->
446
  </div>
447
  </div>
448
  <div id="gd" style="display:none;">
449
+ <div class="gd converter-options">
 
 
 
 
 
 
 
 
450
  <h3>Gd options</h3>
 
 
 
451
  <div>
452
  <label for="gd_skip_pngs">Skip PNGs</label>
453
  <input type="checkbox" id="gd_skip_pngs">
454
+ <br>Gd is not suited for converting PNGs into webp. &ndash;
455
+ The filesize is generally much larger than the original.
456
+ For this reason, the converter defaults to skip PNG's.
457
  </div>
458
  <br>
459
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update and save settings</button>
460
  <!-- <a href="javascript: tb_remove();">close</a> -->
461
  </div>
462
  </div>
463
  <div id="imagick" style="display:none;">
464
  <div class="imagick converter-options">
 
 
 
 
 
465
  <h3>Imagick options</h3>
466
  <div class="info">
467
+ imagick has no special options.
468
  </div>
469
  <br>
470
  <!--
477
  <div class="ewww converter-options">
478
  <h3>Ewww</h3>
479
  <p>
480
+ ewww is a cloud service for converting images.
481
+ To use it, you need to purchase a key <a target="_blank" href="https://ewww.io/plans/">here</a>.
482
+ They do not charge credits for webp conversions, so all you ever have to pay is the one dollar start-up fee :)
 
 
 
483
  </p>
484
+ <h3>Options</h3>
485
  <div>
486
  <label for="ewww_key">Key</label>
487
  <input type="text" id="ewww_key" placeholder="Your API key here">
493
  <input type="text" id="ewww_key_2" placeholder="In case the first one expires...">
494
  </div>
495
  <br>
496
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update and save settings</button>
497
  <!-- <a href="javascript: tb_remove();">close</a> -->
498
  </div>
499
  </div>
500
  <div id="wpc" style="display:none;">
501
  <div class="wpc converter-options">
502
+ <h3>WebPConvert Cloud Service (WPC)</h3>
503
+ wpc is an open source cloud converter based on <a href="https://github.com/rosell-dk/webp-convert" target="_blank">WebPConvert</a>.
504
+ You will need to install the WPC library on a server (or have someone do it for you).
 
 
 
 
505
  <a href="https://github.com/rosell-dk/webp-convert-cloud-service" target="blank">Visit WPC on github</a>.
506
+ (PS: It is planned to integrate wpc into <i>WebP Express</i>, making it very easy to share the capability to convert with your other sites)
507
+ <h3>Options</h3>
 
 
508
  <div>
509
  <label for="wpc_url">URL</label>
510
  <input type="text" id="wpc_url" placeholder="Url to your WPC instance">
514
  <label for="wpc_secret">Secret</label>
515
  <input type="text" id="wpc_secret" placeholder="Secret (must match secret on server side)">
516
  </div>
517
+ <?php
518
+ if ($canDetectQuality) { ?>
519
+ <div>
520
+ <label for="wpc_quality">
521
+ Quality
522
+ <?php echo helpIcon('If "Auto" is selected, the converted image will get same quality as source. Auto is recommended!'); ?>
523
+ </label>
524
+ <!--
525
+ Your server cannot detect quality of jpeg files. But you can have the cloud server do it for you
526
+ (provided that <i>it</i> can) -->
527
+ <select id="wpc_quality" onchange="wpcQualityChanged()">
528
+ <option value="not_set">Use global settings</option>
529
+ <option value="auto">Auto</option>
530
+ </select>
531
+ </div>
532
+ <div id="wpc_max_quality_div">
533
+ <label>
534
+ Max quality
535
+ <?php echo helpIcon('Enter number (0-100). Converted images will be encoded with same quality as the source image, but not more than this setting'); ?>
536
+ </label>
537
+ <input type="text" size=3 id="wpc_max_quality">
538
+ </div>
539
+ <?php } ?>
540
+ <br>
541
  <h4>Fallback (optional)</h4>
542
  <p>In case the first is down, the fallback will be used.</p>
543
  <div>
549
  <input type="text" id="wpc_secret_2" placeholder="Secret (must match secret on server side)">
550
  </div>
551
  <br>
552
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update and save settings</button>
553
  </div>
554
  </div>
555
 
556
  <table>
557
  <tr>
558
+ <td style="padding-right:20px"><?php submit_button('Save settings', 'primary', 'mysubmit'); ?></td>
559
  <td><?php submit_button('Save settings and force new .htaccess rules', 'secondary', 'force'); ?></td>
560
  </tr>
561
  </table>
lib/options/submit.php CHANGED
@@ -17,14 +17,34 @@ use \WebPExpress\Paths;
17
  // https://premium.wpmudev.org/blog/handling-form-submissions/
18
  // checkout https://codex.wordpress.org/Function_Reference/sanitize_meta
19
 
 
 
 
 
 
 
 
20
  $config = [
 
 
 
21
  'fail' => sanitize_text_field($_POST['fail']),
22
- 'max-quality' => sanitize_text_field($_POST['max-quality']),
23
  'image-types' => sanitize_text_field($_POST['image-types']),
24
- 'converters' => json_decode(wp_unslash($_POST['converters']), true), // holy moly! - https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
25
- 'forward-query-string' => true
26
  ];
27
 
 
 
 
 
 
 
 
 
 
 
 
28
  // remove id's
29
  foreach ($config['converters'] as &$converter) {
30
  unset ($converter['id']);
@@ -40,6 +60,11 @@ Messenger::addMessage(
40
  );*/
41
 
42
  /*
 
 
 
 
 
43
  Messenger::addMessage(
44
  'info',
45
  '<pre>' . htmlentities(print_r($result, true)) . '</pre>'
17
  // https://premium.wpmudev.org/blog/handling-form-submissions/
18
  // checkout https://codex.wordpress.org/Function_Reference/sanitize_meta
19
 
20
+ /* We want an integer value between 0-100. We round "77.5" to 78. */
21
+ function webp_express_sanitize_quality_field($text) {
22
+ $text = str_replace(',', '.', $text);
23
+ $q = floatval(sanitize_text_field($text));
24
+ $q = round($q);
25
+ return max(0, min($q, 100));
26
+ }
27
  $config = [
28
+ 'cache-control' => sanitize_text_field($_POST['cache-control']),
29
+ 'cache-control-custom' => sanitize_text_field($_POST['cache-control-custom']),
30
+ 'converters' => json_decode(wp_unslash($_POST['converters']), true), // holy moly! - https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
31
  'fail' => sanitize_text_field($_POST['fail']),
32
+ 'forward-query-string' => true,
33
  'image-types' => sanitize_text_field($_POST['image-types']),
34
+ 'metadata' => sanitize_text_field($_POST['metadata']),
 
35
  ];
36
 
37
+ $auto = (isset($_POST['quality-auto']) && $_POST['quality-auto'] == 'auto_on');
38
+ $config['quality-auto'] = $auto;
39
+
40
+ if ($auto) {
41
+ $config['max-quality'] = webp_express_sanitize_quality_field($_POST['max-quality']);
42
+ $config['quality-specific'] = 70;
43
+ } else {
44
+ $config['max-quality'] = 80;
45
+ $config['quality-specific'] = webp_express_sanitize_quality_field($_POST['quality-specific']);
46
+ }
47
+
48
  // remove id's
49
  foreach ($config['converters'] as &$converter) {
50
  unset ($converter['id']);
60
  );*/
61
 
62
  /*
63
+ Messenger::addMessage(
64
+ 'info',
65
+ '<pre>' . htmlentities(print_r($config, true)) . '</pre>'
66
+ );
67
+
68
  Messenger::addMessage(
69
  'info',
70
  '<pre>' . htmlentities(print_r($result, true)) . '</pre>'
lib/reactivate.php CHANGED
@@ -12,6 +12,9 @@ use \WebPExpress\Messenger;
12
  include_once __DIR__ . '/classes/Actions.php';
13
  use \WebPExpress\Actions;
14
 
 
 
 
15
  include_once __DIR__ . '/classes/PlatformInfo.php';
16
  use \WebPExpress\PlatformInfo;
17
 
@@ -30,7 +33,8 @@ $config = Config::loadConfig();
30
  if ($config === false) {
31
  Messenger::addMessage(
32
  'error',
33
- 'The config file seems to have gone missing. You will need to reconfigure WebP Express <a href="options-general.php?page=webp_express_settings_page">(here)</a>.'
 
34
  );
35
  } else {
36
  $rulesResult = HTAccess::saveRules($config);
@@ -52,14 +56,15 @@ if ($config === false) {
52
  'WebP Express re-activated successfully.<br>' .
53
  'The image redirections are in effect again.<br><br>' .
54
  'Just a quick reminder: If you at some point change the upload directory or move Wordpress, the <i>.htaccess</i> will need to be regenerated.<br>' .
55
- 'You do that by re-saving the settings <a href="options-general.php?page=webp_express_settings_page">(here)</a>'
 
56
  );
57
  } else {
58
  Messenger::addMessage(
59
  'warning',
60
  'WebP Express could not regenerate the rewrite rules<br>' .
61
  'You need to change some permissions. Head to the ' .
62
- '<a href="options-general.php?page=webp_express_settings_page">settings page</a> ' .
63
  'and try to save the settings there (it will provide more information about the problem)'
64
  );
65
 
12
  include_once __DIR__ . '/classes/Actions.php';
13
  use \WebPExpress\Actions;
14
 
15
+ include_once __DIR__ . '/classes/Paths.php';
16
+ use \WebPExpress\Paths;
17
+
18
  include_once __DIR__ . '/classes/PlatformInfo.php';
19
  use \WebPExpress\PlatformInfo;
20
 
33
  if ($config === false) {
34
  Messenger::addMessage(
35
  'error',
36
+ 'The config file seems to have gone missing. You will need to reconfigure WebP Express ' .
37
+ '<a href="' . Paths::getSettingsUrl() . '">(here)</a>.'
38
  );
39
  } else {
40
  $rulesResult = HTAccess::saveRules($config);
56
  'WebP Express re-activated successfully.<br>' .
57
  'The image redirections are in effect again.<br><br>' .
58
  'Just a quick reminder: If you at some point change the upload directory or move Wordpress, the <i>.htaccess</i> will need to be regenerated.<br>' .
59
+ 'You do that by re-saving the settings ' .
60
+ '<a href="' . Paths::getSettingsUrl() . '">(here)</a>'
61
  );
62
  } else {
63
  Messenger::addMessage(
64
  'warning',
65
  'WebP Express could not regenerate the rewrite rules<br>' .
66
  'You need to change some permissions. Head to the ' .
67
+ '<a href="' . Paths::getSettingsUrl() . '">settings page</a> ' .
68
  'and try to save the settings there (it will provide more information about the problem)'
69
  );
70
 
test/small-q61.jpg ADDED
Binary file
test/test-run.php CHANGED
@@ -10,9 +10,8 @@ if (isset($_GET['stream-webp-image'])) {
10
  error_reporting(E_ALL);
11
  ini_set("display_errors", 1);
12
 
13
-
14
-
15
- require "../wod/webp-convert-and-serve.inc";
16
 
17
  use WebPConvert\WebPConvert;
18
  use WebPConvert\Loggers\EchoLogger;
@@ -72,12 +71,18 @@ function getConverterOptionsFromQueryString($converter)
72
  // Set options
73
  $options = [];
74
  foreach ($availOptions as $optionName => $optionType) {
 
75
  switch ($optionType) {
76
  case 'string':
77
  if (isset($_GET[$optionName])) {
78
  $options[$optionName] = $_GET[$optionName];
79
  }
80
  break;
 
 
 
 
 
81
  case 'boolean':
82
  if (isset($_GET[$optionName])) {
83
  $options[$optionName] = ($_GET[$optionName] == 'true');
@@ -92,10 +97,10 @@ $options['converters'] = [[
92
  'options' => getConverterOptionsFromQueryString($converter)
93
  ]];
94
 
 
95
  //echo '<pre>' . print_r($options, true) . '</pre>';
96
 
97
  function testRun($converter, $source, $destination, $options) {
98
- $beginTime = microtime(true);
99
 
100
  try {
101
  $success = WebPConvert::convert($source, $destination, $options, new EchoLogger());
@@ -103,11 +108,8 @@ function testRun($converter, $source, $destination, $options) {
103
  $msg = $e->getMessage();
104
  }
105
 
106
- $endTime = microtime(true);
107
- $duration = $endTime - $beginTime;
108
-
109
  if (!$success) {
110
- echo '<h3 class="error">Test conversion failed (in ' . round($duration * 1000) . ' ms)</h3>';
111
 
112
  if (isset($msg)) {
113
  echo '<label>Problem:</label>';
@@ -116,21 +118,13 @@ function testRun($converter, $source, $destination, $options) {
116
  echo '<p class="error-msg">' . $msg . '</p>';
117
  }
118
  } else {
119
- echo '<p>Successfully converted test image in ' . round($duration * 1000) . ' ms</p>';
120
 
121
  if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
122
  //echo '<img src="' . $_GET['destinationUrl'] . '" width=48%><br><br>';
123
  echo '<img src="?stream-webp-image=' . $destination . '" width=48%><br><br>';
124
 
125
  }
126
- if (filesize($source) < 10000) {
127
- echo 'file size (original): ' . round(filesize($source)) . ' bytes<br>';
128
- echo 'file size (converted): ' . round(filesize($destination)) . ' bytes<br>';
129
- }
130
- else {
131
- echo 'file size (original): ' . round(filesize($source)/1000) . ' kb<br>';
132
- echo 'file size (converted): ' . round(filesize($destination)/1000) . ' kb<br>';
133
- }
134
  }
135
  }
136
 
10
  error_reporting(E_ALL);
11
  ini_set("display_errors", 1);
12
 
13
+ //require "../wod/webp-convert.inc";
14
+ require "../vendor/autoload.php";
 
15
 
16
  use WebPConvert\WebPConvert;
17
  use WebPConvert\Loggers\EchoLogger;
71
  // Set options
72
  $options = [];
73
  foreach ($availOptions as $optionName => $optionType) {
74
+ //echo $optionName . '<br>';
75
  switch ($optionType) {
76
  case 'string':
77
  if (isset($_GET[$optionName])) {
78
  $options[$optionName] = $_GET[$optionName];
79
  }
80
  break;
81
+ case 'number':
82
+ if (isset($_GET[$optionName])) {
83
+ $options[$optionName] = floatval($_GET[$optionName]);
84
+ }
85
+ break;
86
  case 'boolean':
87
  if (isset($_GET[$optionName])) {
88
  $options[$optionName] = ($_GET[$optionName] == 'true');
97
  'options' => getConverterOptionsFromQueryString($converter)
98
  ]];
99
 
100
+ //echo '<pre>' . print_r($_GET, true) . '</pre>';
101
  //echo '<pre>' . print_r($options, true) . '</pre>';
102
 
103
  function testRun($converter, $source, $destination, $options) {
 
104
 
105
  try {
106
  $success = WebPConvert::convert($source, $destination, $options, new EchoLogger());
108
  $msg = $e->getMessage();
109
  }
110
 
 
 
 
111
  if (!$success) {
112
+ echo '<h3 class="error">Test conversion failed</h3>';
113
 
114
  if (isset($msg)) {
115
  echo '<label>Problem:</label>';
118
  echo '<p class="error-msg">' . $msg . '</p>';
119
  }
120
  } else {
121
+ //echo '<p>Successfully converted test image</p>';
122
 
123
  if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
124
  //echo '<img src="' . $_GET['destinationUrl'] . '" width=48%><br><br>';
125
  echo '<img src="?stream-webp-image=' . $destination . '" width=48%><br><br>';
126
 
127
  }
 
 
 
 
 
 
 
 
128
  }
129
  }
130
 
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.5.0
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.6.0
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
wod/Binaries/cwebp-fbsd DELETED
Binary file
wod/Binaries/cwebp-linux DELETED
Binary file
wod/Binaries/cwebp-mac12 DELETED
Binary file
wod/Binaries/cwebp-sol DELETED
Binary file
wod/Binaries/cwebp.exe DELETED
Binary file
wod/webp-convert-and-serve.inc DELETED
@@ -1,1615 +0,0 @@
1
- <?php
2
-
3
-
4
- namespace WebPConvert\Exceptions;
5
-
6
- class WebPConvertBaseException extends \Exception
7
- {
8
- }
9
-
10
-
11
-
12
- namespace WebPConvert\Loggers;
13
-
14
- abstract class BaseLogger
15
- {
16
- /*
17
- $msg: message to log
18
- $style: null | bold | italic
19
- */
20
- abstract public function log($msg, $style = '');
21
-
22
- abstract public function ln();
23
-
24
- public function logLn($msg, $style = '')
25
- {
26
- $this->log($msg, $style);
27
- $this->ln();
28
- }
29
-
30
- public function logLnLn($msg, $style = '')
31
- {
32
- $this->logLn($msg, $style);
33
- $this->ln();
34
- }
35
- }
36
-
37
-
38
-
39
- namespace WebPConvert;
40
-
41
- use WebPConvert\Converters\ConverterHelper;
42
-
43
- class WebPConvert
44
- {
45
-
46
- /*
47
- @param (string) $source: Absolute path to image to be converted (no backslashes). Image must be jpeg or png
48
- @param (string) $destination: Absolute path (no backslashes)
49
- @param (object) $options: Array of named options, such as 'quality' and 'metadata'
50
- */
51
- public static function convert($source, $destination, $options = [], $logger = null)
52
- {
53
- if (!isset($logger)) {
54
- $logger = new \WebPConvert\Loggers\VoidLogger();
55
- }
56
- ConverterHelper::prepareDestinationFolderAndRunCommonValidations($source, $destination);
57
-
58
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
59
-
60
- ConverterHelper::processQualityOption($source, $options, $logger);
61
-
62
- // Force lossless option to true for PNG images
63
- if (ConverterHelper::getExtension($source) == 'png') {
64
- $options['lossless'] = true;
65
- }
66
-
67
- $defaultConverterOptions = $options;
68
- $defaultConverterOptions['converters'] = null;
69
-
70
- $firstFailException = null;
71
-
72
- foreach ($options['converters'] as $converter) {
73
- if (is_array($converter)) {
74
- $converterId = $converter['converter'];
75
- $converterOptions = $converter['options'];
76
- } else {
77
- $converterId = $converter;
78
- $converterOptions = [];
79
- }
80
-
81
- $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
82
-
83
- try {
84
- $logger->logLn('Trying:' . $converterId, 'italic');
85
-
86
- // If quality is different, we must recalculate
87
- if ($converterOptions['quality'] != $defaultConverterOptions['quality']) {
88
- unset($converterOptions['_calculated_quality']);
89
- ConverterHelper::processQualityOption($source, $converterOptions, $logger);
90
- }
91
-
92
- ConverterHelper::runConverter($converterId, $source, $destination, $converterOptions, false, $logger);
93
-
94
- // Still here? - well, we did it! - job is done.
95
- $logger->logLn('ok', 'bold');
96
- return true;
97
- } catch (\WebPConvert\Converters\Exceptions\ConverterNotOperationalException $e) {
98
- // $logger->logLnLn($e->description . ' : ' . $e->getMessage());
99
- $logger->logLnLn($e->getMessage());
100
-
101
- // The converter is not operational.
102
- // Well, well, we will just have to try the next, then
103
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
104
- $logger->logLnLn($e->getMessage());
105
-
106
- // Converter failed in an anticipated, yet somewhat surprising fashion.
107
- // The converter seemed operational - requirements was in order - but it failed anyway.
108
- // This is moderately bad.
109
- // If some other converter can handle the conversion, we will let this one go.
110
- // But if not, we shall throw the exception
111
-
112
- if (!$firstFailException) {
113
- $firstFailException = $e;
114
- }
115
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
116
- $logger->logLnLn($e->getMessage());
117
-
118
- // The converter declined.
119
- // Gd is for example throwing this, when asked to convert a PNG, but configured not to
120
- // We also possibly rethrow this, because it may have come as a surprise to the user
121
- // who perhaps only tested jpg
122
- if (!$firstFailException) {
123
- $firstFailException = $e;
124
- }
125
- }
126
- }
127
-
128
- $logger->logLn('Conversion failed. None of the tried converters are operational', 'bold');
129
-
130
- // No converters could do the job.
131
- // If one of them failed moderately bad, rethrow that exception.
132
- if ($firstFailException) {
133
- throw $firstFailException;
134
- }
135
-
136
- return false;
137
- }
138
- }
139
-
140
-
141
-
142
- namespace WebPConvert\Converters;
143
-
144
- //use WebPConvert\Converters\Cwebp;
145
-
146
- use WebPConvert\Exceptions\ConverterNotFoundException;
147
- use WebPConvert\Exceptions\CreateDestinationFileException;
148
- use WebPConvert\Exceptions\CreateDestinationFolderException;
149
- use WebPConvert\Exceptions\InvalidFileExtensionException;
150
- use WebPConvert\Exceptions\TargetNotFoundException;
151
-
152
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
153
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
154
-
155
- class ConverterHelper
156
- {
157
- public static $allowedExtensions = ['jpg', 'jpeg', 'png'];
158
-
159
- public static $defaultOptions = [
160
- 'quality' => 'auto',
161
- 'max-quality' => 85,
162
- 'default-quality' => 80,
163
- 'metadata' => 'none',
164
- 'method' => 6,
165
- 'low-memory' => false,
166
- 'lossless' => false,
167
- 'converters' => ['cwebp', 'gd', 'imagick']
168
- ];
169
-
170
- public static function mergeOptions($options, $extraOptions)
171
- {
172
- return $options;
173
- }
174
-
175
- public static function getClassNameOfConverter($converterId)
176
- {
177
- return 'WebPConvert\\Converters\\' . ucfirst($converterId);
178
- }
179
-
180
-
181
- /* Call the "convert" method on a converter, by id.
182
- - but also prepares options (merges in the $extraOptions of the converter),
183
- prepares destination folder, and runs some standard validations */
184
- public static function runConverter($converterId, $source, $destination, $options = [], $prepareDestinationFolder = true, $logger = null)
185
- {
186
- if ($prepareDestinationFolder) {
187
- ConverterHelper::prepareDestinationFolderAndRunCommonValidations($source, $destination);
188
- }
189
-
190
- if (!isset($logger)) {
191
- $logger = new \WebPConvert\Loggers\VoidLogger();
192
- }
193
-
194
- $className = self::getClassNameOfConverter($converterId);
195
- if (!is_callable([$className, 'convert'])) {
196
- throw new ConverterNotFoundException();
197
- }
198
-
199
- // Prepare options.
200
- // - Remove 'converters'
201
- $defaultOptions = self::$defaultOptions;
202
- unset($defaultOptions['converters']);
203
-
204
- // - Merge defaults of the converters extra options into the standard default options.
205
- $defaultOptions = array_merge($defaultOptions, array_column($className::$extraOptions, 'default', 'name'));
206
-
207
- // - Merge $defaultOptions into provided options
208
- $options = array_merge($defaultOptions, $options);
209
-
210
- // Individual converters do not accept quality = auto. They need a number.
211
- // Change $options['quality'] to number, based on quality of source and several settings
212
-
213
- self::processQualityOption($source, $options, $logger);
214
-
215
- call_user_func(
216
- [$className, 'doConvert'],
217
- $source,
218
- $destination,
219
- $options,
220
- $logger
221
- );
222
-
223
- if (!file_exists($destination)) {
224
- throw new ConverterFailedException('Destination file is not there');
225
- }
226
- }
227
-
228
- /* Try to detect quality of jpeg.
229
- If not possible, nothing is returned (null). Otherwise quality is returned (int)
230
- */
231
- public static function detectQualityOfJpg($filename)
232
- {
233
- // Try Imagick extension
234
- if (extension_loaded('imagick') && class_exists('Imagick')) {
235
- $img = new Imagick($filename);
236
-
237
- // The required function is available as from PECL imagick v2.2.2
238
- if (method_exists($img, 'getImageCompressionQuality')) {
239
- return $img->getImageCompressionQuality();
240
- }
241
- }
242
-
243
- if (function_exists('shell_exec')) {
244
-
245
- // Try Imagick
246
- $quality = shell_exec("identify -format '%Q' " . $filename);
247
- if ($quality) {
248
- return intval($quality);
249
- }
250
-
251
- // Try GraphicsMagick
252
- $quality = shell_exec("gm identify -format '%Q' " . $filename);
253
- if ($quality) {
254
- return intval($quality);
255
- }
256
- }
257
- }
258
-
259
- public static function processQualityOption($source, &$options, $logger)
260
- {
261
- if (isset($options['_calculated_quality'])) {
262
- return;
263
- }
264
- if ($options['quality'] == 'auto') {
265
- $q = self::detectQualityOfJpg($source);
266
- //$logger->log('Quality set to auto... Quality of source: ');
267
- if (!$q) {
268
- $q = $options['default-quality'];
269
- $logger->logLn('Quality of source could not be established (Imagick or GraphicsMagick is required) - Using default instead (' . $options['default-quality'] . ').');
270
-
271
- // this allows the wpc converter to know
272
- $options['_quality_could_not_be_detected'] = true;
273
- } else {
274
- if ($q > $options['max-quality']) {
275
- $logger->log('Quality of source is ' . $q . '. This is higher than max-quality, so using that instead (' . $options['max-quality'] . ')');
276
- } else {
277
- $logger->log('Quality set to same as source: ' . $q);
278
- }
279
- }
280
- $logger->ln();
281
- $q = min($q, $options['max-quality']);
282
-
283
- $options['_calculated_quality'] = $q;
284
- //$logger->logLn('Using quality: ' . $options['quality']);
285
- } else {
286
- $logger->logLn('Quality: ' . $options['quality'] . '. Consider setting quality to "auto" instead. It is generally a better idea');
287
- $options['_calculated_quality'] = $options['quality'];
288
- }
289
- $logger->ln();
290
- }
291
-
292
-
293
- public static function getExtension($filePath)
294
- {
295
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
296
- return strtolower($fileExtension);
297
- }
298
-
299
- // Throws an exception if the provided file doesn't exist
300
- public static function isValidTarget($filePath)
301
- {
302
- if (!file_exists($filePath)) {
303
- throw new TargetNotFoundException('File or directory not found: ' . $filePath);
304
- }
305
-
306
- return true;
307
- }
308
-
309
- // Throws an exception if the provided file's extension is invalid
310
- public static function isAllowedExtension($filePath)
311
- {
312
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
313
- if (!in_array(strtolower($fileExtension), self::$allowedExtensions)) {
314
- throw new InvalidFileExtensionException('Unsupported file extension: ' . $fileExtension);
315
- }
316
-
317
- return true;
318
- }
319
-
320
- // Creates folder in provided path & sets correct permissions
321
- public static function createWritableFolder($filePath)
322
- {
323
- $folder = pathinfo($filePath, PATHINFO_DIRNAME);
324
- if (!file_exists($folder)) {
325
- // TODO: what if this is outside open basedir?
326
- // see http://php.net/manual/en/ini.core.php#ini.open-basedir
327
-
328
- // First, we have to figure out which permissions to set.
329
- // We want same permissions as parent folder
330
- // But which parent? - the parent to the first missing folder
331
-
332
- $parentFolders = explode('/', $folder);
333
- $poppedFolders = [];
334
-
335
- while (!(file_exists(implode('/', $parentFolders))) && count($parentFolders) > 0) {
336
- array_unshift($poppedFolders, array_pop($parentFolders));
337
- }
338
-
339
- // Retrieving permissions of closest existing folder
340
- $closestExistingFolder = implode('/', $parentFolders);
341
- $permissions = fileperms($closestExistingFolder) & 000777;
342
-
343
- // Trying to create the given folder
344
- // Notice: mkdir emits a warning on failure. It would be nice to suppress that, if possible
345
- if (!mkdir($folder, $permissions, true)) {
346
- throw new CreateDestinationFolderException('Failed creating folder: ' . $folder);
347
- }
348
-
349
-
350
- // `mkdir` doesn't respect permissions, so we have to `chmod` each created subfolder
351
- foreach ($poppedFolders as $subfolder) {
352
- $closestExistingFolder .= '/' . $subfolder;
353
- // Setting directory permissions
354
- chmod($folder, $permissions);
355
- }
356
- }
357
-
358
- // Checks if there's a file in $filePath & if writing permissions are correct
359
- if (file_exists($filePath) && !is_writable($filePath)) {
360
- throw new CreateDestinationFileException('Cannot overwrite ' . basename($filePath) . ' - check file permissions.');
361
- }
362
-
363
- // There's either a rewritable file in $filePath or none at all.
364
- // If there is, simply attempt to delete it
365
- if (file_exists($filePath) && !unlink($filePath)) {
366
- throw new CreateDestinationFileException('Existing file cannot be removed: ' . basename($filePath));
367
- }
368
-
369
- return true;
370
- }
371
-
372
- public static function prepareDestinationFolderAndRunCommonValidations($source, $destination)
373
- {
374
- self::isValidTarget($source);
375
- self::isAllowedExtension($source);
376
- self::createWritableFolder($destination);
377
- }
378
-
379
- public static function initCurlForConverter()
380
- {
381
- if (!extension_loaded('curl')) {
382
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
383
- }
384
-
385
- if (!function_exists('curl_init')) {
386
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
387
- }
388
-
389
- if (!function_exists('curl_file_create')) {
390
- throw new ConverterNotOperationalException('Required curl_file_create() function is not available (requires PHP > 5.5).');
391
- }
392
-
393
- $ch = curl_init();
394
- if (!$ch) {
395
- throw new ConverterNotOperationalException('Could not initialise cURL.');
396
- }
397
- return $ch;
398
- }
399
- }
400
-
401
-
402
-
403
- namespace WebPConvert\Converters;
404
-
405
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
406
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
407
-
408
- class Cwebp
409
- {
410
- public static $extraOptions = [
411
- [
412
- 'name' => 'use-nice',
413
- 'type' => 'boolean',
414
- 'sensitive' => false,
415
- 'default' => false,
416
- 'required' => false
417
- ],
418
- ];
419
-
420
- public static function convert($source, $destination, $options = [])
421
- {
422
- ConverterHelper::runConverter('cwebp', $source, $destination, $options, true);
423
- }
424
-
425
- // System paths to look for cwebp binary
426
- private static $cwebpDefaultPaths = [
427
- '/usr/bin/cwebp',
428
- '/usr/local/bin/cwebp',
429
- '/usr/gnu/bin/cwebp',
430
- '/usr/syno/bin/cwebp'
431
- ];
432
-
433
- // OS-specific binaries included in this library, along with hashes
434
- private static $suppliedBinariesInfo = [
435
- 'WinNT' => [ 'cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
436
- 'Darwin' => [ 'cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
437
- 'SunOS' => [ 'cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
438
- 'FreeBSD' => [ 'cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
439
- 'Linux' => [ 'cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568']
440
- ];
441
-
442
- private static function escapeFilename($string)
443
- {
444
- // Escaping whitespaces & quotes
445
- $string = preg_replace('/\s/', '\\ ', $string);
446
- $string = filter_var($string, FILTER_SANITIZE_MAGIC_QUOTES);
447
-
448
- // Stripping control characters
449
- // see https://stackoverflow.com/questions/12769462/filter-flag-strip-low-vs-filter-flag-strip-high
450
- $string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
451
-
452
- return $string;
453
- }
454
-
455
- // Checks if 'Nice' is available
456
- private static function hasNiceSupport()
457
- {
458
- exec("nice 2>&1", $niceOutput);
459
-
460
- if (is_array($niceOutput) && isset($niceOutput[0])) {
461
- if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
462
- /*
463
- * Nice is available - default niceness (+10)
464
- * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
465
- * https://www.computerhope.com/unix/unice.htm
466
- */
467
-
468
- return true;
469
- }
470
-
471
- return false;
472
- }
473
- }
474
-
475
- //
476
- private static function executeBinary($binary, $commandOptions, $useNice, $logger)
477
- {
478
- $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions;
479
-
480
- $logger->logLn('Trying to execute binary:' . $binary);
481
- //$logger->logLn();
482
-
483
- exec($command, $output, $returnCode);
484
-
485
- switch ($returnCode) {
486
- case 0:
487
- $logger->logLn('Success!');
488
- break;
489
- case 126:
490
- $logger->logLn('Permission denied. The user that the command was run with (' . shell_exec('whoami') . ') does not have permission to execute that binary.');
491
- break;
492
- case 127:
493
- $logger->logLn('No binary found at that location');
494
- break;
495
- default:
496
- $logger->logLn('Failed. Return code:' . $returnCode . '. See http://tldp.org/LDP/abs/html/exitcodes.html for failcodes');
497
- }
498
- return $returnCode;
499
- }
500
-
501
- // Although this method is public, do not call directly.
502
- public static function doConvert($source, $destination, $options = [], $logger)
503
- {
504
- $errorMsg = '';
505
- // Force lossless option to true for PNG images
506
- if (ConverterHelper::getExtension($source) == 'png') {
507
- $options['lossless'] = true;
508
- }
509
-
510
- if (!function_exists('exec')) {
511
- throw new ConverterNotOperationalException('exec() is not enabled.');
512
- }
513
-
514
- /*
515
- * Prepare cwebp options
516
- */
517
-
518
- // Metadata (all, exif, icc, xmp or none (default))
519
- // Comma-separated list of existing metadata to copy from input to output
520
- $metadata = '-metadata ' . $options['metadata'];
521
-
522
- // Image quality
523
- $quality = '-q ' . $options['_calculated_quality'];
524
-
525
- // Losless PNG conversion
526
- $lossless = ($options['lossless'] ? '-lossless' : '');
527
-
528
- // Built-in method option
529
- $method = ' -m ' . strval($options['method']);
530
-
531
-
532
- // TODO:
533
- // Why not use -af ? (https://developers.google.com/speed/webp/docs/cwebp)
534
- // Would it be possible get a quality similar to source?
535
- // It seems so: "identify -format '%Q' yourimage.jpg" (https://stackoverflow.com/questions/2024947/is-it-possible-to-tell-the-quality-level-of-a-jpeg)
536
- // -- With -jpeg_like option, or perhaps the -size option
537
-
538
- // Built-in low memory option
539
- $lowMemory = '';
540
- if ($options['low-memory']) {
541
- $lowMemory = '-low_memory';
542
- }
543
-
544
- $commandOptionsArray = [
545
- $metadata = $metadata,
546
- $quality = $quality,
547
- $lossless = $lossless,
548
- $method = $method,
549
- $lowMemory = $lowMemory,
550
- $input = self::escapeFilename($source),
551
- $output = '-o ' . self::escapeFilename($destination),
552
- $stderrRedirect = '2>&1'
553
- ];
554
-
555
- $useNice = (($options['use-nice']) && self::hasNiceSupport()) ? true : false;
556
-
557
- $commandOptions = implode(' ', $commandOptionsArray);
558
-
559
-
560
- // Init with common system paths
561
- $cwebpPathsToTest = self::$cwebpDefaultPaths;
562
-
563
- // Remove paths that doesn't exist
564
- $cwebpPathsToTest = array_filter($cwebpPathsToTest, function ($binary) {
565
- //return file_exists($binary);
566
- return @is_readable($binary);
567
- });
568
-
569
- // Try all common paths that exitst
570
- $success = false;
571
- foreach ($cwebpPathsToTest as $index => $binary) {
572
- $success = (self::executeBinary($binary, $commandOptions, $useNice, $logger) == 0);
573
- if ($success) {
574
- break;
575
- }
576
- }
577
- if (!$success) {
578
- //$logger->logLn('');
579
- if (count($cwebpPathsToTest) > 0) {
580
- $errorMsg .= 'Found cwebp binaries at these locations: "' . implode('", "', $cwebpPathsToTest) . '". However, executing these failed. ';
581
- } else {
582
- $errorMsg .= 'Found no cwebp binaries in any common locations. ';
583
- }
584
- }
585
-
586
- if (!$success) {
587
-
588
- // Try supplied binary (if available for OS, and hash is correct)
589
- if (isset(self::$suppliedBinariesInfo[PHP_OS])) {
590
- $info = self::$suppliedBinariesInfo[PHP_OS];
591
-
592
- $file = $info[0];
593
- $hash = $info[1];
594
-
595
- $binaryFile = __DIR__ . '/Binaries/' . $file;
596
-
597
- // The file should exist, but may have been removed manually.
598
- if (file_exists($binaryFile)) {
599
- // File exists, now generate its hash
600
- $binaryHash = hash_file('sha256', $binaryFile);
601
-
602
- // Throw an exception if binary file checksum & deposited checksum do not match
603
- if ($binaryHash != $hash) {
604
- //throw new ConverterNotOperationalException('Binary checksum is invalid.');
605
- $errorMsg .= 'Binary checksum of supplied binary is invalid! Did you transfer with FTP, but not in binary mode? File:' . $binaryFile . '. Expected checksum: ' . $hash . ' Actual checksum:' . $binaryHash . '. ';
606
- } else {
607
- $returnCode = self::executeBinary($binaryFile, $commandOptions, $useNice, $logger);
608
- if ($returnCode == 0) {
609
- $success = true;
610
- } else {
611
- $errorMsg .= 'Tried executing supplied binary (' . $binaryFile . '), but that failed too: ';
612
- switch ($returnCode) {
613
- case 126:
614
- $errorMsg .= 'Permission denied (user "' . trim(shell_exec('whoami')) . '" does not have permission to execute the binary)';
615
- break;
616
- default:
617
- $errorMsg .= 'Fail code: ' . $returnCode;
618
- }
619
- }
620
- }
621
- } else {
622
- $errorMsg .= 'Supplied binary not found:' . $binaryFile;
623
- }
624
- } else {
625
- $errorMsg .= 'No supplied binaries found for OS:' . PHP_OS;
626
- }
627
- }
628
-
629
-
630
-
631
- // cwebp sets file permissions to 664 but instead ..
632
- // .. $destination's parent folder's permissions should be used (except executable bits)
633
- if ($success) {
634
- $destinationParent = dirname($destination);
635
- $fileStatistics = stat($destinationParent);
636
-
637
- // Apply same permissions as parent folder but strip off the executable bits
638
- $permissions = $fileStatistics['mode'] & 0000666;
639
- chmod($destination, $permissions);
640
- }
641
-
642
- if (!$success) {
643
- throw new ConverterNotOperationalException($errorMsg);
644
- }
645
- }
646
- }
647
-
648
-
649
-
650
- namespace WebPConvert\Converters;
651
-
652
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
653
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
654
-
655
- class Ewww
656
- {
657
- public static $extraOptions = [
658
- [
659
- 'name' => 'key',
660
- 'type' => 'string',
661
- 'sensitive' => true,
662
- 'default' => '',
663
- 'required' => true
664
- ],
665
- ];
666
-
667
- public static function convert($source, $destination, $options = [])
668
- {
669
- ConverterHelper::runConverter('ewww', $source, $destination, $options, true);
670
- }
671
-
672
- // Although this method is public, do not call directly.
673
- public static function doConvert($source, $destination, $options = [], $logger)
674
- {
675
- if ($options['key'] == '') {
676
- throw new ConverterNotOperationalException('Missing API key.');
677
- }
678
- if (strlen($options['key']) < 20) {
679
- throw new ConverterNotOperationalException('Key is invalid. Keys are supposed to be 32 characters long - your key is much shorter');
680
- }
681
-
682
- $keyStatus = self::getKeyStatus($options['key']);
683
- switch ($keyStatus) {
684
- case 'great':
685
- break;
686
- case 'exceeded':
687
- throw new ConverterNotOperationalException('quota has exceeded');
688
- break;
689
- case 'invalid':
690
- throw new ConverterNotOperationalException('key is invalid');
691
- break;
692
- }
693
-
694
- $ch = ConverterHelper::initCurlForConverter();
695
-
696
- $curlOptions = [
697
- 'api_key' => $options['key'],
698
- 'webp' => '1',
699
- 'file' => curl_file_create($source),
700
- 'domain' => $_SERVER['HTTP_HOST'],
701
- 'quality' => $options['_calculated_quality'],
702
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
703
- ];
704
-
705
- curl_setopt_array($ch, [
706
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
707
- CURLOPT_HTTPHEADER => [
708
- 'User-Agent: WebPConvert',
709
- 'Accept: image/*'
710
- ],
711
- CURLOPT_POSTFIELDS => $curlOptions,
712
- CURLOPT_BINARYTRANSFER => true,
713
- CURLOPT_RETURNTRANSFER => true,
714
- CURLOPT_HEADER => false,
715
- CURLOPT_SSL_VERIFYPEER => false
716
- ]);
717
-
718
- $response = curl_exec($ch);
719
-
720
- if (curl_errno($ch)) {
721
- throw new ConverterNotOperationalException(curl_error($ch));
722
- }
723
-
724
- // The API does not always return images.
725
- // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
726
- // Messages has a http content type of ie 'text/html; charset=UTF-8
727
- // Images has application/octet-stream.
728
- // So verify that we got an image back.
729
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
730
-
731
- //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
732
- curl_close($ch);
733
-
734
- /* May return this: {"error":"invalid","t":"exceeded"} */
735
- $responseObj = json_decode($response);
736
- if (isset($responseObj->error)) {
737
- //echo 'error:' . $responseObj->error . '<br>';
738
- //echo $response;
739
- //self::blacklistKey($key);
740
- //throw new ConverterNotOperationalException('The key is invalid. Blacklisted it!');
741
- throw new ConverterNotOperationalException('The key is invalid');
742
- }
743
-
744
- throw new ConverterNotOperationalException('ewww api did not return an image. It could be that the key is invalid. Response: ' . $response);
745
- }
746
-
747
- // Not sure this can happen. So just in case
748
- if ($response == '') {
749
- throw new ConverterNotOperationalException('ewww api did not return anything');
750
- }
751
-
752
- $success = file_put_contents($destination, $response);
753
-
754
- if (!$success) {
755
- throw new ConverterFailedException('Error saving file');
756
- }
757
- }
758
-
759
- /*
760
- public static function blacklistKey($key)
761
- {
762
- }
763
-
764
- public static function isKeyBlacklisted($key)
765
- {
766
- }*/
767
-
768
- /**
769
- * Return "great", "exceeded" or "invalid"
770
- */
771
- public static function getKeyStatus($key)
772
- {
773
- $ch = ConverterHelper::initCurlForConverter();
774
-
775
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
776
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
777
- curl_setopt($ch, CURLOPT_POSTFIELDS, [
778
- 'api_key' => $key
779
- ]);
780
-
781
- // The 403 forbidden is avoided with this line.
782
- curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)');
783
-
784
- $response = curl_exec($ch);
785
- // echo $response;
786
- if (curl_errno($ch)) {
787
- throw new \Exception(curl_error($ch));
788
- }
789
- curl_close($ch);
790
-
791
- // Possible responses:
792
- // “great” = verification successful
793
- // “exceeded” = indicates a valid key with no remaining image credits.
794
- // an empty response indicates that the key is not valid
795
-
796
- if ($response == '') {
797
- return 'invalid';
798
- }
799
- $responseObj = json_decode($response);
800
- if (isset($responseObj->error)) {
801
- if ($responseObj->error == 'invalid') {
802
- return 'invalid';
803
- } else {
804
- throw new \Exception('Ewww returned unexpected error: ' . $response);
805
- }
806
- }
807
- if (!isset($responseObj->status)) {
808
- throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
809
- }
810
- switch ($responseObj->status) {
811
- case 'great':
812
- case 'exceeded':
813
- return $responseObj->status;
814
- }
815
- throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
816
- }
817
-
818
- public static function isWorkingKey($key)
819
- {
820
- return (self::getKeyStatus($key) == 'great');
821
- }
822
-
823
- public static function isValidKey($key)
824
- {
825
- return (self::getKeyStatus($key) != 'invalid');
826
- }
827
-
828
- public static function getQuota($key)
829
- {
830
- $ch = ConverterHelper::initCurlForConverter();
831
-
832
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
833
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
834
- curl_setopt($ch, CURLOPT_POSTFIELDS, [
835
- 'api_key' => $key
836
- ]);
837
- curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)');
838
-
839
- $response = curl_exec($ch);
840
- return $response; // ie -830 23. Seems to return empty for invalid keys
841
- // or empty
842
- //echo $response;
843
- }
844
- }
845
-
846
-
847
-
848
- namespace WebPConvert\Converters;
849
-
850
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
851
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
852
- use WebPConvert\Converters\Exceptions\ConversionDeclinedException;
853
-
854
- use WebPConvert\Converters\ConverterHelper;
855
-
856
- class Gd
857
- {
858
- public static $extraOptions = [
859
- [
860
- 'name' => 'skip-pngs',
861
- 'type' => 'boolean',
862
- 'sensitive' => false,
863
- 'default' => true,
864
- 'required' => false
865
- ],
866
- ];
867
-
868
- public static function convert($source, $destination, $options = [])
869
- {
870
- ConverterHelper::runConverter('gd', $source, $destination, $options, true);
871
- }
872
-
873
- // Although this method is public, do not call directly.
874
- public static function doConvert($source, $destination, $options = [], $logger)
875
- {
876
- if (!extension_loaded('gd')) {
877
- throw new ConverterNotOperationalException('Required GD extension is not available.');
878
- }
879
-
880
- if (!function_exists('imagewebp')) {
881
- throw new ConverterNotOperationalException('Required imagewebp() function is not available.');
882
- }
883
-
884
- switch (ConverterHelper::getExtension($source)) {
885
- case 'png':
886
- if (!$options['skip-pngs']) {
887
- if (!function_exists('imagecreatefrompng')) {
888
- throw new ConverterNotOperationalException('Required imagecreatefrompng() function is not available.');
889
- }
890
- $image = imagecreatefrompng($source);
891
- if (!$image) {
892
- throw new ConverterFailedException('imagecreatefrompng("' . $source . '") failed');
893
- }
894
- } else {
895
- throw new ConversionDeclinedException('PNG file skipped. GD is configured not to convert PNGs');
896
- }
897
- break;
898
- default:
899
- if (!function_exists('imagecreatefromjpeg')) {
900
- throw new ConverterNotOperationalException('Required imagecreatefromjpeg() function is not available.');
901
- }
902
- $image = imagecreatefromjpeg($source);
903
- if (!$image) {
904
- throw new ConverterFailedException('imagecreatefromjpeg("' . $source . '") failed');
905
- }
906
- }
907
-
908
- // Checks if either imagecreatefromjpeg() or imagecreatefrompng() returned false
909
-
910
- $success = imagewebp($image, $destination, $options['_calculated_quality']);
911
-
912
- if (!$success) {
913
- throw new ConverterFailedException('Call to imagewebp() failed. Probably failed writing file');
914
- }
915
-
916
- /*
917
- * This hack solves an `imagewebp` bug
918
- * See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
919
- *
920
- */
921
-
922
- if (filesize($destination) % 2 == 1) {
923
- file_put_contents($destination, "\0", FILE_APPEND);
924
- }
925
-
926
- imagedestroy($image);
927
- }
928
- }
929
-
930
-
931
-
932
- namespace WebPConvert\Converters;
933
-
934
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
935
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
936
-
937
- //use WebPConvert\Exceptions\TargetNotFoundException;
938
-
939
- class Imagick
940
- {
941
- public static $extraOptions = [];
942
-
943
- public static function convert($source, $destination, $options = [])
944
- {
945
- ConverterHelper::runConverter('imagick', $source, $destination, $options, true);
946
- }
947
-
948
- // Although this method is public, do not call directly.
949
- public static function doConvert($source, $destination, $options = [], $logger)
950
- {
951
- if (!extension_loaded('imagick')) {
952
- throw new ConverterNotOperationalException('Required iMagick extension is not available.');
953
- }
954
-
955
- if (!class_exists('Imagick')) {
956
- throw new ConverterNotOperationalException('iMagick is installed, but not correctly. The class Imagick is not available');
957
- }
958
-
959
- $im = new \Imagick($source);
960
-
961
- // Throws an exception if iMagick does not support WebP conversion
962
- if (!in_array('WEBP', $im->queryFormats())) {
963
- throw new ConverterNotOperationalException('iMagick was compiled without WebP support.');
964
- }
965
-
966
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
967
-
968
- // Force lossless option to true for PNG images
969
- if (ConverterHelper::getExtension($source) == 'png') {
970
- $options['lossless'] = true;
971
- }
972
-
973
- $im->setImageFormat('WEBP');
974
-
975
- /*
976
- * More about iMagick's WebP options:
977
- * http://www.imagemagick.org/script/webp.php
978
- * https://developers.google.com/speed/webp/docs/cwebp
979
- * https://stackoverflow.com/questions/37711492/imagemagick-specific-webp-calls-in-php
980
- */
981
-
982
- // TODO: We could easily support all webp options with a loop
983
- $im->setOption('webp:method', strval($options['method']));
984
- $im->setOption('webp:low-memory', strval($options['low-memory']));
985
- $im->setOption('webp:lossless', strval($options['lossless']));
986
-
987
-
988
-
989
- $im->setImageCompressionQuality($options['_calculated_quality']);
990
-
991
- // TODO:
992
- // Should we set alpha channel for PNG's like suggested here:
993
- // https://gauntface.com/blog/2014/09/02/webp-support-with-imagemagick-and-php ??
994
- // It seems that alpha channel works without... (at least I see completely transparerent pixels)
995
-
996
- // TODO: Check out other iMagick methods, see http://php.net/manual/de/imagick.writeimage.php#114714
997
- // 1. file_put_contents($destination, $im)
998
- // 2. $im->writeImage($destination)
999
- $success = $im->writeImageFile(fopen($destination, 'wb'));
1000
-
1001
- if (!$success) {
1002
- throw new ConverterFailedException('Failed writing file');
1003
- }
1004
- }
1005
- }
1006
-
1007
-
1008
-
1009
- namespace WebPConvert\Converters;
1010
-
1011
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
1012
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
1013
-
1014
- class Wpc
1015
- {
1016
- public static $extraOptions = [
1017
- [
1018
- 'name' => 'secret',
1019
- 'type' => 'string',
1020
- 'sensitive' => true,
1021
- 'default' => 'my dog is white',
1022
- 'required' => true
1023
- ],
1024
- [
1025
- 'name' => 'url',
1026
- 'type' => 'string',
1027
- 'sensitive' => true,
1028
- 'default' => '',
1029
- 'required' => true
1030
- ],
1031
- ];
1032
-
1033
- public static function convert($source, $destination, $options = [])
1034
- {
1035
- ConverterHelper::runConverter('wpc', $source, $destination, $options, true);
1036
- }
1037
-
1038
- // Although this method is public, do not call directly.
1039
- public static function doConvert($source, $destination, $options = [], $logger)
1040
- {
1041
- if ($options['url'] == '') {
1042
- throw new ConverterNotOperationalException('Missing URL. You must install WebpConvertCloudService on a server, and supply url');
1043
- }
1044
-
1045
- if (!extension_loaded('curl')) {
1046
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
1047
- }
1048
-
1049
- if (!function_exists('curl_init')) {
1050
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
1051
- }
1052
-
1053
- if (!function_exists('curl_file_create')) {
1054
- throw new ConverterNotOperationalException('Required curl_file_create() function is not available (requires PHP > 5.5).');
1055
- }
1056
-
1057
- // Got some code here:
1058
- // https://coderwall.com/p/v4ps1a/send-a-file-via-post-with-curl-and-php
1059
-
1060
- $ch = curl_init();
1061
- if (!$ch) {
1062
- throw new ConverterNotOperationalException('Could not initialise cURL.');
1063
- }
1064
-
1065
- $optionsToSend = $options;
1066
-
1067
- if (isset($options['_quality_could_not_be_detected'])) {
1068
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
1069
- // Ask the cloud service to do it, rather than using what we came up with.
1070
- $optionsToSend['quality'] = 'auto';
1071
- } else {
1072
- $optionsToSend['quality'] = $options['_calculated_quality'];
1073
- }
1074
-
1075
- unset($optionsToSend['converters']);
1076
- unset($optionsToSend['secret']);
1077
- unset($optionsToSend['_quality_could_not_be_detected']);
1078
- unset($optionsToSend['_calculated_quality']);
1079
-
1080
- curl_setopt_array($ch, [
1081
- CURLOPT_URL => $options['url'],
1082
- CURLOPT_POST => 1,
1083
- CURLOPT_POSTFIELDS => [
1084
- 'file' => curl_file_create($source),
1085
- 'hash' => md5(md5_file($source) . $options['secret']),
1086
- 'options' => json_encode($optionsToSend)
1087
- ],
1088
- CURLOPT_BINARYTRANSFER => true,
1089
- CURLOPT_RETURNTRANSFER => true,
1090
- CURLOPT_HEADER => false,
1091
- CURLOPT_SSL_VERIFYPEER => false
1092
- ]);
1093
-
1094
- $response = curl_exec($ch);
1095
-
1096
- if (curl_errno($ch)) {
1097
- throw new ConverterNotOperationalException(curl_error($ch));
1098
- }
1099
-
1100
- // The WPC cloud service either returns an image or an error message
1101
- // Images has application/octet-stream.
1102
-
1103
- // TODO: Check for 404 response, and handle that here
1104
-
1105
- // Verify that we got an image back.
1106
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
1107
- curl_close($ch);
1108
- throw new ConverterFailedException($response);
1109
- //throw new ConverterNotOperationalException($response);
1110
- }
1111
-
1112
- $success = file_put_contents($destination, $response);
1113
- curl_close($ch);
1114
-
1115
- if (!$success) {
1116
- throw new ConverterFailedException('Error saving file');
1117
- }
1118
- /*
1119
- $curlOptions = [
1120
- 'api_key' => $options['key'],
1121
- 'webp' => '1',
1122
- 'file' => curl_file_create($source),
1123
- 'domain' => $_SERVER['HTTP_HOST'],
1124
- 'quality' => $options['quality'],
1125
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
1126
- ];
1127
-
1128
- curl_setopt_array($ch, [
1129
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
1130
- CURLOPT_HTTPHEADER => [
1131
- 'User-Agent: WebPConvert',
1132
- 'Accept: image/*'
1133
- ],
1134
- CURLOPT_POSTFIELDS => $curlOptions,
1135
- CURLOPT_BINARYTRANSFER => true,
1136
- CURLOPT_RETURNTRANSFER => true,
1137
- CURLOPT_HEADER => false,
1138
- CURLOPT_SSL_VERIFYPEER => false
1139
- ]);*/
1140
- }
1141
- }
1142
-
1143
-
1144
-
1145
- namespace WebPConvert\Exceptions;
1146
-
1147
- use WebPConvert\Exceptions\WebPConvertBaseException;
1148
-
1149
- class ConverterNotFoundException extends WebPConvertBaseException
1150
- {
1151
- public $description = 'The converter does not exist.';
1152
- }
1153
-
1154
-
1155
-
1156
- namespace WebPConvert\Exceptions;
1157
-
1158
- use WebPConvert\Exceptions\WebPConvertBaseException;
1159
-
1160
- class CreateDestinationFileException extends WebPConvertBaseException
1161
- {
1162
- public $description = 'The converter could not create destination file. Check file permisions!';
1163
- }
1164
-
1165
-
1166
-
1167
- namespace WebPConvert\Exceptions;
1168
-
1169
- use WebPConvert\Exceptions\WebPConvertBaseException;
1170
-
1171
- class CreateDestinationFolderException extends WebPConvertBaseException
1172
- {
1173
- public $description = 'The converter could not create destination folder. Check file permisions!';
1174
- }
1175
-
1176
-
1177
-
1178
- namespace WebPConvert\Exceptions;
1179
-
1180
- use WebPConvert\Exceptions\WebPConvertBaseException;
1181
-
1182
- class InvalidFileExtensionException extends WebPConvertBaseException
1183
- {
1184
- public $description = 'The converter does not accept the file extension';
1185
- }
1186
-
1187
-
1188
-
1189
- namespace WebPConvert\Exceptions;
1190
-
1191
- use WebPConvert\Exceptions\WebPConvertBaseException;
1192
-
1193
- class TargetNotFoundException extends WebPConvertBaseException
1194
- {
1195
- public $description = 'The converter could not locate source file';
1196
- }
1197
-
1198
-
1199
-
1200
- namespace WebPConvert\Converters\Exceptions;
1201
-
1202
- use WebPConvert\Exceptions\WebPConvertBaseException;
1203
-
1204
- class ConversionDeclinedException extends WebPConvertBaseException
1205
- {
1206
- public $description = 'The converter declined converting';
1207
- }
1208
-
1209
-
1210
-
1211
- namespace WebPConvert\Converters\Exceptions;
1212
-
1213
- use WebPConvert\Exceptions\WebPConvertBaseException;
1214
-
1215
- class ConverterFailedException extends WebPConvertBaseException
1216
- {
1217
- public $description = 'The converter failed converting, although requirements seemed to be met';
1218
- }
1219
-
1220
-
1221
-
1222
- namespace WebPConvert\Converters\Exceptions;
1223
-
1224
- use WebPConvert\Exceptions\WebPConvertBaseException;
1225
-
1226
- class ConverterNotOperationalException extends WebPConvertBaseException
1227
- {
1228
- public $description = 'The converter is not operational';
1229
- }
1230
-
1231
-
1232
-
1233
- namespace WebPConvert\Loggers;
1234
-
1235
- class EchoLogger extends BaseLogger
1236
- {
1237
- public function log($msg, $style = '')
1238
- {
1239
- if ($style == 'bold') {
1240
- echo '<b>' . $msg . '</b>';
1241
- } elseif ($style == 'italic') {
1242
- echo '<i>' . $msg . '</i>';
1243
- } else {
1244
- echo $msg;
1245
- }
1246
- }
1247
-
1248
- public function ln()
1249
- {
1250
- echo '<br>';
1251
- }
1252
- }
1253
-
1254
-
1255
-
1256
- namespace WebPConvert\Loggers;
1257
-
1258
- class VoidLogger extends BaseLogger
1259
- {
1260
- public function log($msg, $style = '')
1261
- {
1262
- }
1263
-
1264
- public function ln()
1265
- {
1266
- }
1267
- }
1268
-
1269
-
1270
-
1271
- namespace WebPConvertAndServe;
1272
- use WebPConvert\Loggers\BaseLogger;
1273
-
1274
- class BufferLogger extends BaseLogger
1275
- {
1276
- public $entries = array();
1277
-
1278
- public function log($msg, $style = '')
1279
- {
1280
- $this->entries[] = [$msg, $style];
1281
- }
1282
-
1283
- public function ln()
1284
- {
1285
- $this->entries[] = '';
1286
- }
1287
-
1288
- public function getHtml()
1289
- {
1290
- $html = '';
1291
- foreach ($this->entries as $entry) {
1292
- if ($entry == '') {
1293
- $html .= '<br>';
1294
- } else {
1295
- list($msg, $style) = $entry;
1296
-
1297
- if ($style == 'bold') {
1298
- $html .= '<b>' . $msg . '</b>';
1299
- } elseif ($style == 'italic') {
1300
- $html .= '<i>' . $msg . '</i>';
1301
- } else {
1302
- $html .= $msg;
1303
- }
1304
- }
1305
- }
1306
- return $html;
1307
- }
1308
-
1309
- public function getText($newLineChar = ' ')
1310
- {
1311
- $text = '';
1312
- foreach ($this->entries as $entry) {
1313
- if ($entry == '') {
1314
- if (substr($text, -2) != '. ') {
1315
- $text .= '. ';
1316
- }
1317
- } else {
1318
- list($msg, $style) = $entry;
1319
- $text .= $msg;
1320
- }
1321
- }
1322
-
1323
- return $text;
1324
- }
1325
- }
1326
-
1327
-
1328
- namespace WebPConvertAndServe;
1329
-
1330
- use WebPConvert\WebPConvert;
1331
- use WebPConvertAndServe\BufferLogger;
1332
- use WebPConvert\Converters\ConverterHelper;
1333
- //use WebPConvert\Loggers\EchoLogger;
1334
-
1335
- class WebPConvertAndServe
1336
- {
1337
- public static $CONVERTED_IMAGE = 1;
1338
- public static $ORIGINAL = -1;
1339
- public static $HTTP_404 = -2;
1340
- public static $REPORT_AS_IMAGE = -3;
1341
- public static $REPORT = -4;
1342
-
1343
- public static $defaultOptions = [
1344
- 'fail' => 'original',
1345
- 'critical-fail' => '404',
1346
- ];
1347
-
1348
- private static function serve404()
1349
- {
1350
- $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
1351
- header($protocol . " 404 Not Found");
1352
- }
1353
-
1354
- private static function serveOriginal($source)
1355
- {
1356
- // Prevent caching image
1357
- header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
1358
- header("Cache-Control: post-check=0, pre-check=0", false);
1359
- header("Pragma: no-cache");
1360
-
1361
- $arr = explode('.', $source);
1362
- $ext = array_pop($arr);
1363
- switch (strtolower($ext)) {
1364
- case 'jpg':
1365
- case 'jpeg':
1366
- header('Content-type: image/jpeg');
1367
- break;
1368
- case 'png':
1369
- header('Content-type: image/png');
1370
- break;
1371
- }
1372
- readfile($source);
1373
- }
1374
-
1375
- private static function serveErrorMessageImage($msg)
1376
- {
1377
- // Generate image containing error message
1378
- header('Content-type: image/gif');
1379
-
1380
- // Prevent caching image
1381
- header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
1382
- header("Cache-Control: post-check=0, pre-check=0", false);
1383
- header("Pragma: no-cache");
1384
-
1385
- $image = imagecreatetruecolor(620, 200);
1386
- imagestring($image, 1, 5, 5, $msg, imagecolorallocate($image, 233, 214, 291));
1387
- // echo imagewebp($image);
1388
- echo imagegif($image);
1389
- imagedestroy($image);
1390
- }
1391
-
1392
- /**
1393
- * @depreciated
1394
- */
1395
- public static function convertAndServeImage($source, $destination, $options, $failAction, $criticalFailAction, $debug = false)
1396
- {
1397
- if ($debug) {
1398
- error_reporting(E_ALL);
1399
- ini_set('display_errors', 'On');
1400
- } else {
1401
- ini_set('display_errors', 'Off');
1402
- }
1403
-
1404
- $options['fail'] = $failAction;
1405
- $options['critical-fail'] = $criticalFailAction;
1406
-
1407
- return self::convertAndServe($source, $destination, $options);
1408
- }
1409
-
1410
- /**
1411
- * Main method
1412
- */
1413
- public static function convertAndServe($source, $destination, $options)
1414
- {
1415
- $options = array_merge(self::$defaultOptions, $options);
1416
-
1417
- $failCodes = [
1418
- "original" => -1,
1419
- "404" => -2,
1420
- "report-as-image" => -3,
1421
- "report" => -4,
1422
- ];
1423
-
1424
- $failAction = $options['fail'];
1425
- $criticalFailAction = $options['critical-fail'];
1426
-
1427
- if (is_string($failAction)) {
1428
- $failAction = $failCodes[$failAction];
1429
- }
1430
- if (is_string($criticalFailAction)) {
1431
- $criticalFailAction = $failCodes[$criticalFailAction];
1432
- }
1433
-
1434
- $criticalFail = false;
1435
- $success = false;
1436
- $bufferLogger = new BufferLogger();
1437
-
1438
- try {
1439
- $success = WebPConvert::convert($source, $destination, $options, $bufferLogger);
1440
-
1441
- if ($success) {
1442
- $status = 'Success';
1443
- $msg = 'Success';
1444
- } else {
1445
- $status = 'Failure (no converters are operational)';
1446
- $msg = 'No converters are operational';
1447
- }
1448
- } catch (\WebPConvert\Exceptions\InvalidFileExtensionException $e) {
1449
- $criticalFail = true;
1450
- $status = 'Failure (invalid file extension)';
1451
- $msg = $e->getMessage();
1452
- } catch (\WebPConvert\Exceptions\TargetNotFoundException $e) {
1453
- $criticalFail = true;
1454
- $status = 'Failure (target file not found)';
1455
- $msg = $e->getMessage();
1456
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
1457
- // No converters could convert the image. At least one converter failed, even though it appears to be operational
1458
- $status = 'Failure (no converters could convert the image)';
1459
- $msg = $e->getMessage();
1460
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
1461
- // (no converters could convert the image. At least one converter declined
1462
- $status = 'Failure (no converters could/wanted to convert the image)';
1463
- $msg = $e->getMessage();
1464
- } catch (\WebPConvert\Exceptions\ConverterNotFoundException $e) {
1465
- $status = 'Failure (a converter was not found!)';
1466
- $msg = $e->getMessage();
1467
- } catch (\WebPConvert\Exceptions\CreateDestinationFileException $e) {
1468
- $status = 'Failure (cannot create destination file)';
1469
- $msg = $e->getMessage();
1470
- } catch (\WebPConvert\Exceptions\CreateDestinationFolderException $e) {
1471
- $status = 'Failure (cannot create destination folder)';
1472
- $msg = $e->getMessage();
1473
- } catch (\Exception $e) {
1474
- $status = 'Failure (an unanticipated exception was thrown)';
1475
- $msg = $e->getMessage();
1476
- }
1477
-
1478
- $optionsForPrint = [];
1479
- foreach (self::getPrintableOptions($options) as $optionName => $optionValue) {
1480
- if ($optionName == 'converters') {
1481
- $converterNames = [];
1482
- $extraConvertOptions = [];
1483
- foreach ($optionValue as $converter) {
1484
- if (is_array($converter)) {
1485
- $converterNames[] = $converter['converter'];
1486
- if (isset($converter['options'])) {
1487
- $extraConvertOptions[$converter['converter']] = $converter['options'];
1488
- }
1489
- } else {
1490
- $converterNames[] = $converter;
1491
- }
1492
- }
1493
- $optionsForPrint[] = 'converters:' . implode(',', $converterNames);
1494
- foreach ($extraConvertOptions as $converter => $extraOptions) {
1495
- $opt = [];
1496
- foreach ($extraOptions as $oName => $oValue) {
1497
- $opt[] = $oName . ':"' . $oValue . '"';
1498
- }
1499
- $optionsForPrint[] = $converter . ' options:(' . implode($opt, ', ') . ')';
1500
- }
1501
- } else {
1502
- $optionsForPrint[] = $optionName . ':' . $optionValue ;
1503
- }
1504
-
1505
- }
1506
-
1507
- header('X-WebP-Convert-And-Serve-Options:' . implode('. ', $optionsForPrint));
1508
-
1509
- header('X-WebP-Convert-And-Serve-Status: ' . $status);
1510
-
1511
- // Next line is commented out, because we need to be absolute sure that the details does not violate header syntax
1512
- // We could either try to filter it, or we could change WebPConvert, such that it only provides safe texts.
1513
- // header('X-WebP-Convert-And-Serve-Details: ' . $bufferLogger->getText());
1514
-
1515
- if ($success) {
1516
- header('Content-type: image/webp');
1517
- // Should we add Content-Length header?
1518
- // header('Content-Length: ' . filesize($file));
1519
- readfile($destination);
1520
- return self::$CONVERTED_IMAGE;
1521
- } else {
1522
- $action = ($criticalFail ? $criticalFailAction : $failAction);
1523
-
1524
- switch ($action) {
1525
- case WebPConvertAndServe::$ORIGINAL:
1526
- self::serveOriginal($source);
1527
- break;
1528
- case WebPConvertAndServe::$HTTP_404:
1529
- self::serve404();
1530
- break;
1531
- case WebPConvertAndServe::$REPORT_AS_IMAGE:
1532
- self::serveErrorMessageImage($status . '. ' . $msg);
1533
- break;
1534
- case WebPConvertAndServe::$REPORT:
1535
- echo '<h1>' . $status . '</h1>';
1536
- echo $msg;
1537
- echo '<p>This is how conversion process went:</p>' . $bufferLogger->getHtml();
1538
- break;
1539
- }
1540
- return $action;
1541
- }
1542
- }
1543
-
1544
- /* Hides sensitive options */
1545
- private static function getPrintableOptions($options)
1546
- {
1547
-
1548
- $printable_options = [];
1549
-
1550
- // (psst: the is_callable check is needed in order to work with WebPConvert v1.0)
1551
- if (is_callable('ConverterHelper', 'getClassNameOfConverter')) {
1552
-
1553
- $printable_options = $options;
1554
- if (isset($printable_options['converters'])) {
1555
- foreach ($printable_options['converters'] as &$converter) {
1556
- if (is_array($converter)) {
1557
- //echo '::' . $converter['converter'] . '<br>';
1558
- $className = ConverterHelper::getClassNameOfConverter($converter['converter']);
1559
-
1560
- // (pstt: the isset check is needed in order to work with WebPConvert v1.0)
1561
- if (isset($className::$extraOptions)) {
1562
- foreach ($className::$extraOptions as $extraOption) {
1563
- if ($extraOption['sensitive']) {
1564
- if (isset($converter['options'][$extraOption['name']])) {
1565
- $converter['options'][$extraOption['name']] = '*******';
1566
- }
1567
- }
1568
- }
1569
- }
1570
- }
1571
- }
1572
- }
1573
- }
1574
- return $printable_options;
1575
- }
1576
-
1577
- public static function convertAndReport($source, $destination, $options)
1578
- {
1579
- error_reporting(E_ALL);
1580
- ini_set('display_errors', 'On');
1581
-
1582
- echo '<html><style>td {vertical-align: top} table {color: #666}</style>';
1583
- echo '<body><table>';
1584
- echo '<tr><td><i>source:</i></td><td>' . $source . '</td></tr>';
1585
- echo '<tr><td><i>destination:</i></td><td>' . $destination . '<td></tr>';
1586
-
1587
- echo '<tr><td><i>options:</i></td><td>' . print_r(self::getPrintableOptions($options), true) . '</td></tr>';
1588
- echo '</table>';
1589
-
1590
- // TODO:
1591
- // We could display warning if unknown options are set
1592
- // but that requires that WebPConvert also describes its general options
1593
-
1594
- echo '<br>';
1595
-
1596
- try {
1597
- $echoLogger = new \WebPConvert\Loggers\EchoLogger();
1598
- $success = WebPConvert::convert($source, $destination, $options, $echoLogger);
1599
- } catch (\Exception $e) {
1600
- $success = false;
1601
-
1602
- $msg = $e->getMessage();
1603
-
1604
- echo '<b>' . $msg . '</b>';
1605
- exit;
1606
- }
1607
-
1608
- if ($success) {
1609
- //echo 'ok';
1610
- } else {
1611
- echo '<b>Conversion failed. None of the tried converters are operational</b>';
1612
- }
1613
- echo '</body></html>';
1614
- }
1615
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
wod/webp-on-demand.inc DELETED
@@ -1,135 +0,0 @@
1
- <?php
2
-
3
-
4
- namespace WebPOnDemand;
5
-
6
- class WebPOnDemand
7
- {
8
- public static $defaultOptions = [
9
- 'show-report' => false,
10
- 'reconvert' => false,
11
- 'original' => false,
12
- 'add-x-webp-on-demand-headers' => true,
13
- 'add-vary-header' => true,
14
- ];
15
-
16
- private static function serveOriginal($source)
17
- {
18
- // Serve original image
19
- $arr = explode('.', $source);
20
- $ext = array_pop($arr);
21
- switch (strtolower($ext)) {
22
- case 'jpg':
23
- case 'jpeg':
24
- header('Content-type: image/jpeg');
25
- break;
26
- case 'png':
27
- header('Content-type: image/png');
28
- break;
29
- }
30
- if (@readfile($source) === false) {
31
- header('X-WebP-On-Demand-Error: Could not read file');
32
- }
33
- }
34
-
35
- private static function addWebPOnDemandHeader($text, $options)
36
- {
37
- if ($options['add-x-webp-on-demand-headers']) {
38
- header('X-WebP-On-Demand: ' . $text);
39
- }
40
- }
41
-
42
- private static function addVaryHeader($options)
43
- {
44
- if ($options['add-vary-header']) {
45
- header('Vary: Accept');
46
- }
47
- }
48
-
49
-
50
- public static function serve($source, $destination, $options)
51
- {
52
- $options = array_merge(self::$defaultOptions, $options);
53
-
54
- if (empty($source)) {
55
- self::addWebPOnDemandHeader('Failed (Missing source argument)', $options);
56
- }
57
- if (empty($destination)) {
58
- self::addWebPOnDemandHeader('Failed (Missing destination argument)', $options);
59
- }
60
-
61
- if ($options['show-report']) {
62
- // Load WebPConvertAndServe (only when needed)
63
- if (isset($options['require-for-conversion'])) {
64
- require($options['require-for-conversion']);
65
- }
66
-
67
- self::addWebPOnDemandHeader('Reporting...', $options);
68
-
69
- \WebPConvertAndServe\WebPConvertAndServe::convertAndReport($source, $destination, $options);
70
- return;
71
- }
72
-
73
- if ($options['original']) {
74
- self::addWebPOnDemandHeader('Serving original image (was explicitly told to)', $options);
75
- self::serveOriginal($source);
76
- }
77
- if (file_exists($destination) && (!$options['reconvert'])) {
78
- $timestampSource = filemtime($source);
79
- $timestampDestination = filemtime($destination);
80
-
81
- if (($timestampSource === false) &&
82
- ($timestampDestination !== false) &&
83
- ($timestampSource > $timestampDestination)) {
84
- // It must be reconverted...
85
- // will be done in a subsequent block...
86
- } else {
87
- $filesizeDestination = filesize($destination);
88
- $filesizeSource = filesize($source);
89
-
90
- // Serve original image, if the converted image is larger
91
- if (($filesizeSource !== false) &&
92
- ($filesizeDestination !== false) &&
93
- ($filesizeDestination > $filesizeSource)) {
94
- self::addWebPOnDemandHeader(
95
- 'Serving original image - because it is smaller than the converted!',
96
- $options
97
- );
98
- self::addVaryHeader($options);
99
- self::serveOriginal($source);
100
- } else {
101
- // Serve existing converted image
102
- //echo $destination;
103
- header('Content-type: image/webp');
104
- self::addWebPOnDemandHeader('Serving existing converted image', $options);
105
- self::addVaryHeader($options);
106
-
107
- if (@readfile($destination) === false) {
108
- header('X-WebP-On-Demand-Error: Could not read file');
109
- }
110
- }
111
- return;
112
- }
113
- }
114
-
115
- // We are still here... This means we should ignite the converter, possibly doing a reconversion
116
-
117
- // Load WebPConvertAndServe (we only do that when it is needed)
118
- if (isset($options['require-for-conversion'])) {
119
- require($options['require-for-conversion']);
120
- }
121
-
122
- self::addWebPOnDemandHeader('Converting image (handed over to WebPConvertAndServe)', $options);
123
-
124
- // We do not add "Vary Accept" header here, because WebPConvertAndServe will do that (if successful)
125
-
126
- unset($options['show-report']);
127
- unset($options['reconvert']);
128
- unset($options['original']);
129
- unset($options['add-x-webp-on-demand-headers']);
130
- unset($options['require-for-conversion']);
131
-
132
-
133
- \WebPConvertAndServe\WebPConvertAndServe::convertAndServe($source, $destination, $options);
134
- }
135
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
wod/webp-on-demand.php CHANGED
@@ -1,8 +1,10 @@
1
  <?php
2
 
3
- require 'webp-on-demand.inc';
 
 
4
 
5
- use \WebPOnDemand\WebPOnDemand;
6
 
7
  $options = [];
8
 
@@ -15,7 +17,25 @@ fclose($handle);
15
  $options = json_decode($json, true);
16
  //print_r($options);
17
 
18
- $options['require-for-conversion'] = 'webp-convert-and-serve.inc';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  if ($options['forward-query-string']) {
21
  if (isset($_GET['debug'])) {
@@ -47,4 +67,4 @@ if (substr($source, 0, strlen($applicationRoot)) === $applicationRoot) {
47
  //echo $source . '<br>';
48
  //echo $destination . '<br>';
49
  //echo $sourceRel;
50
- WebPOnDemand::serve($source, $destination, $options);
1
  <?php
2
 
3
+ //require 'webp-on-demand-1.inc';
4
+ require '../vendor/rosell-dk/webp-convert/build/webp-on-demand-1.inc';
5
+ //require '../vendor/autoload.php';
6
 
7
+ use \WebPConvert\WebPConvert;
8
 
9
  $options = [];
10
 
17
  $options = json_decode($json, true);
18
  //print_r($options);
19
 
20
+ $options['require-for-conversion'] = 'webp-on-demand-2.inc';
21
+
22
+ $gmagickHere = false;
23
+ foreach ($options['converters'] as &$converter) {
24
+ if (isset($converter['converter'])) {
25
+ $converterId = $converter['converter'];
26
+ } else {
27
+ $converterId = $converter;
28
+ }
29
+ if ($converterId == 'cwebp') {
30
+ $converter['options']['rel-path-to-precompiled-binaries'] = '../src/Converters/Binaries';
31
+ }
32
+ if ($converterId == 'gmagick') {
33
+ $gmagickHere = true;
34
+ }
35
+ }
36
+ if (!$gmagickHere) {
37
+ $options['converters'][] = 'gmagick';
38
+ }
39
 
40
  if ($options['forward-query-string']) {
41
  if (isset($_GET['debug'])) {
67
  //echo $source . '<br>';
68
  //echo $destination . '<br>';
69
  //echo $sourceRel;
70
+ WebPConvert::convertAndServe($source, $destination, $options);