WebP Express - Version 0.5.0

Version Description

This version works on many more setups than the previous. Also uses less resources and handles when images are changed.

  • 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.
  • Handles setups where Wordpress has been given its own directory (both methods mentioned here)
  • Handles setups where wp-content has been moved, even out of Wordpress root.
  • Handles setups where Uploads folder has been moved, even out of wp-content.
  • Handles setups where Plugins folder has been moved, even out of wp-content or out of Wordpress root
  • 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)
  • 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
  • The .htaccess now passes the complete absulute path to the source file instead of a relative path. This is a less error-prone method.
  • Reconverts the webp, if source image has changed
  • Now runs on version 1.0.0 of WebP On Demand. Previously ran on 0.3.0
  • 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)
  • Now works in Wordpress 4.0 - 4.6.
  • Added cache-breaking tokens to image test links
  • Denies deactivation if rewrite rules could not be removed
  • Refactored thoroughly
  • More helpful texts.
  • 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.

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

=

Download this release

Release Info

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

Code changes from version 0.4.0 to 0.5.0

Files changed (73) hide show
  1. README.md +29 -11
  2. README.txt +34 -28
  3. changelog.txt +11 -0
  4. lib/activate-first-time.php +65 -0
  5. lib/activate-hook.php +11 -0
  6. lib/activate.php +0 -88
  7. lib/admin.php +49 -0
  8. lib/classes/Actions.php +46 -0
  9. lib/classes/Config.php +165 -0
  10. lib/classes/FileHelper.php +163 -0
  11. lib/classes/HTAccess.php +458 -0
  12. lib/classes/Messenger.php +87 -0
  13. lib/classes/PathHelper.php +119 -0
  14. lib/classes/Paths.php +311 -0
  15. lib/classes/PlatformInfo.php +65 -0
  16. lib/classes/State.php +42 -0
  17. lib/deactivate.php +32 -6
  18. lib/debug.php +12 -0
  19. lib/helpers.php +0 -311
  20. lib/message.php +0 -131
  21. lib/migrate/migrate.php +47 -0
  22. lib/migrate/migrate1.php +205 -0
  23. lib/options.php +0 -498
  24. {css → lib/options/css}/webp-express-options-page.css +12 -2
  25. lib/options/enqueue_scripts.php +32 -0
  26. {images → lib/options/images}/drag-reorder.svg +0 -0
  27. {js → lib/options/js}/sortable.min.js +0 -0
  28. {js → lib/options/js}/webp-express-options-page.js +5 -46
  29. lib/options/options-hooks.php +29 -0
  30. lib/options/page-messages.php +87 -0
  31. lib/options/page-welcome.php +126 -0
  32. lib/options/page.php +306 -0
  33. lib/options/submit.php +177 -0
  34. lib/reactivate.php +68 -0
  35. lib/uninstall.php +39 -0
  36. test-run.php +0 -260
  37. test/test-run.php +137 -0
  38. vendor/require-webp-convert-and-serve.php +0 -4
  39. vendor/require-webp-convert.php +0 -20
  40. vendor/require-webp-on-demand.php +0 -3
  41. vendor/webp-convert-and-serve/BufferLogger.php +0 -57
  42. vendor/webp-convert-and-serve/WebPConvertAndServe.php +0 -254
  43. vendor/webp-convert/Converters/ConverterHelper.php +0 -260
  44. vendor/webp-convert/Converters/Cwebp.php +0 -259
  45. vendor/webp-convert/Converters/Ewww.php +0 -197
  46. vendor/webp-convert/Converters/Exceptions/ConversionDeclinedException.php +0 -10
  47. vendor/webp-convert/Converters/Exceptions/ConverterFailedException.php +0 -10
  48. vendor/webp-convert/Converters/Exceptions/ConverterNotOperationalException.php +0 -10
  49. vendor/webp-convert/Converters/Gd.php +0 -83
  50. vendor/webp-convert/Converters/Imagick.php +0 -76
  51. vendor/webp-convert/Converters/Wpc.php +0 -171
  52. vendor/webp-convert/Exceptions/ConverterNotFoundException.php +0 -10
  53. vendor/webp-convert/Exceptions/CreateDestinationFileException.php +0 -10
  54. vendor/webp-convert/Exceptions/CreateDestinationFolderException.php +0 -10
  55. vendor/webp-convert/Exceptions/InvalidFileExtensionException.php +0 -10
  56. vendor/webp-convert/Exceptions/TargetNotFoundException.php +0 -10
  57. vendor/webp-convert/Exceptions/WebPConvertBaseException.php +0 -7
  58. vendor/webp-convert/Loggers/BaseLogger.php +0 -26
  59. vendor/webp-convert/Loggers/EchoLogger.php +0 -22
  60. vendor/webp-convert/Loggers/VoidLogger.php +0 -14
  61. vendor/webp-convert/WebPConvert.php +0 -108
  62. vendor/webp-convert/require-all.inc +0 -20
  63. vendor/webp-on-demand/WebPOnDemand.php +0 -345
  64. webp-express.php +3 -101
  65. webp-on-demand.php +0 -17
  66. {vendor/webp-convert/Converters → wod}/Binaries/cwebp-fbsd +0 -0
  67. {vendor/webp-convert/Converters → wod}/Binaries/cwebp-linux +0 -0
  68. {vendor/webp-convert/Converters → wod}/Binaries/cwebp-mac12 +0 -0
  69. {vendor/webp-convert/Converters → wod}/Binaries/cwebp-sol +0 -0
  70. {vendor/webp-convert/Converters → wod}/Binaries/cwebp.exe +0 -0
  71. wod/webp-convert-and-serve.inc +1615 -0
  72. wod/webp-on-demand.inc +135 -0
  73. wod/webp-on-demand.php +50 -0
README.md CHANGED
@@ -35,9 +35,9 @@ These different converting methods are called *converters*.
35
  The best converter is *cwebp*. So the first thing you should do is test whether the cwebp converter is working. Simply click "test" next to the converter. If it doesn't work, you can disable that converter (or try to make it work, by changing the server setup).
36
  The next converter to try is *wpc*, which is equally good as cwebp in terms of quality / filesize ratio, but which is slower. wpc is an open source cloud service, which you will have to install on some other server. If this is too much work, continue to the next converter. In the converter settings, you can read about the individual converters. You can also head to the WebPConvert readme for more information.
37
 
38
- Once, you have a converter, that works, when you click the "test"-button, you are ready to test the whole stack, and the rewrite rules. To do this, first make sure to select something other than "Do not convert any images!" in *Image types to convert*. Next, click "Save settings". This will save settings, as well as update the .htaccess.
39
 
40
- If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" just above the "Save settings" button. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the .htaccess redirection isn't working. It may be that your server just needs some time. Some servers has set up caching.
41
 
42
  Note that the plugin does not change any HTML. In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
43
 
@@ -47,17 +47,17 @@ 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 Routed to image converter" the first time, but "Routed to existing converted image" on subsequent requests (WebP-Express is based upon 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 sensitive to the location of your image folder and the location of Wordpress. If you at some point change one of these, the rules will have to be updated. .htaccess rules are updated whenever you change a setting (all configuration is actually stored in .htaccess, which allows the converter to run faster, than if it had the overhead of bootstrapping Wordpress)
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!
@@ -67,12 +67,7 @@ The plugin has not been tested in multisite configurations. It's on the roadmap!
67
 
68
  * The plugin does not work on Microsoft IIS server
69
  * The plugin has not been tested with multisite installation (it is on the roadmap!).
70
- * The plugin has only been tested in Wordpress 4.7.5 and above, but I expect it to work in other versions.
71
- * The plugin has not been tested in all possible Wordpress configurations. It has been tested in the following configurations: root install, subdir install, and subdir install with redirect from root (described as method 1 (here)[https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory]).
72
- * There might be compatability issues with other plugins. For example .htaccess rules from other plugins might interfere.
73
-
74
- ## Known compatability issues
75
- * W3TotalCache: When CDN is enabled, W3TotalCache creates some .htaccess rules which interferes when they appear before the rules created by this plugin. You can move them by manually editing the .htaccess file
76
 
77
  ## Frequently Asked Questions
78
 
@@ -87,6 +82,29 @@ The plugin takes care of setting the "Vary" HTTP header to "Accept" when routing
87
  ### How do I donate?
88
  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.
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  # Roadmap
91
 
92
  * Share converter with other sites. Optionally provide conversion service for other sites, which will be able to connect to it using the "wpc" converter.
35
  The best converter is *cwebp*. So the first thing you should do is test whether the cwebp converter is working. Simply click "test" next to the converter. If it doesn't work, you can disable that converter (or try to make it work, by changing the server setup).
36
  The next converter to try is *wpc*, which is equally good as cwebp in terms of quality / filesize ratio, but which is slower. wpc is an open source cloud service, which you will have to install on some other server. If this is too much work, continue to the next converter. In the converter settings, you can read about the individual converters. You can also head to the WebPConvert readme for more information.
37
 
38
+ Once, you have a converter, that works, when you click the "test"-button, you are ready to test the whole stack, and the rewrite rules. To do this, first make sure to select something other than "Do not convert any images!" in *Image types to convert*. Next, click "Save settings". This will save settings, as well as update the *.htaccess*.
39
 
40
+ If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching.
41
 
42
  Note that the plugin does not change any HTML. In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
43
 
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!
67
 
68
  * The plugin does not work on Microsoft IIS server
69
  * The plugin has not been tested with multisite installation (it is on the roadmap!).
70
+ * There might be compatability issues with other plugins. For example *.htaccess* rules from other plugins might interfere.
 
 
 
 
 
71
 
72
  ## Frequently Asked Questions
73
 
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
 
110
  * Share converter with other sites. Optionally provide conversion service for other sites, which will be able to connect to it using the "wpc" converter.
README.txt CHANGED
@@ -2,10 +2,10 @@
2
  Contributors: rosell.dk
3
  Donate link: https://www.bitwise-it.dk/contact
4
  Tags: webp, images, performance
5
- Requires at least: 4.7
6
  Tested up to: 4.9
7
- Stable tag: 0.4.0
8
- Requires PHP: 5.5
9
  License: GPLv3
10
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
11
 
@@ -45,9 +45,9 @@ The best converter is cwebp. So the first thing you should do is test whether th
45
 
46
  The next converter to try is wpc, which is equally good as cwebp in terms of quality / filesize ratio, but which is slower. wpc is an open source cloud service, which you will have to install on some other server. It is btw on the roadmap to have this plugin provide the same service. If installing wpc too much work, continue to the next converter. I will not go through all of them here; you can learn more by clicking on the "configure" button on a converter. You can also head to the WebPConvert project for more information.
47
 
48
- Once, you have a converter, that works, when you click the "test"-button, you are ready to test the whole stack, and the rewrite rules. To do this, first make sure to select something other than "Do not convert any images!" in "Image types to convert". Next, click "Save settings". This will save settings, as well as update the .htaccess.
49
 
50
- If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" just above the "Save settings" button. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the .htaccess redirection isn't working. It may be that your server just needs some time. Some servers has set up caching.
51
 
52
  Note that the plugin does not change any HTML. In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
53
 
@@ -57,7 +57,7 @@ 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 Routed to image converter" the first time, but "Routed to existing converted image" on subsequent requests (WebP-Express is based upon 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.
@@ -65,24 +65,19 @@ Btw: If you append ?reconvert after an image url, you will force a reconversion
65
  ### Notes
66
 
67
  *Note:*
68
- The redirect rules created in .htaccess are sensitive to the location of your image folder and the location of Wordpress. If you at some point change one of these, the rules will have to be updated. .htaccess rules are updated whenever you change a setting (all configuration is actually stored in .htaccess, which allows the converter to run faster, than if it had the overhead of bootstrapping Wordpress)
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
- == Screenshots ==
77
-
78
-
79
  == Limitations ==
80
 
81
  * The plugin does not work on Microsoft IIS server
82
  * The plugin has not been tested with multisite installation (it is on the roadmap!).
83
- * The plugin has only been tested in Wordpress 4.7.5 and above, but I expect it to work in other versions.
84
- * The plugin has not been tested in all possible Wordpress configurations. It has been tested in the following configurations: root install, subdir install, and subdir install with redirect from root (described as method 1 (here)[https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory]).
85
- * There might be compatability issues with other plugins. For example .htaccess rules from other plugins might interfere.
86
 
87
  == Frequently Asked Questions ==
88
 
@@ -105,24 +100,35 @@ Putting this question in the "frequently" asked questions section is of course s
105
 
106
  == Changelog ==
107
 
108
- = 0.4.0 =
109
- * Fixed bug: .htaccess was not updated every time the settings was saved.
110
- * Fixed bug: The plugin generated error upon activation.
111
- * Now produces X-WebP-Convert-And-Serve headers with info about the conversion - useful for validating that converter receives the expected arguments and executes correctly.
112
- * WebPExpress options are now removed when plugin is uninstalled.
113
- * No longer generates .htaccess rules on install. The user now has to actively go to Web Express setting and save first
114
- * Added a "first time" message on options page and a reactivation message
115
-
116
- For more info, see the closed issues on the github repository: https://github.com/rosell-dk/webp-express/milestone/1?closed=1
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  == Upgrade Notice ==
119
 
120
- = 0.4.0 =
121
- This version fixes some misbehaviours and provides new http headers with info about the conversion process.
122
 
123
  == Roadmap ==
124
 
125
  * Share converter with other sites. Optionally provide conversion service for other sites, which will be able to connect to it using the "wpc" converter.
126
- * Test on multisite
127
- * Display whether the server is able to detect quality of jpegs or not
128
- * Make the fallback quality configurable (the quality to use, when quality of source file cannot be determined)
2
  Contributors: rosell.dk
3
  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
11
 
45
 
46
  The next converter to try is wpc, which is equally good as cwebp in terms of quality / filesize ratio, but which is slower. wpc is an open source cloud service, which you will have to install on some other server. It is btw on the roadmap to have this plugin provide the same service. If installing wpc too much work, continue to the next converter. I will not go through all of them here; you can learn more by clicking on the "configure" button on a converter. You can also head to the WebPConvert project for more information.
47
 
48
+ Once, you have a converter, that works, when you click the "test"-button, you are ready to test the whole stack, and the rewrite rules. To do this, first make sure to select something other than "Do not convert any images!" in "Image types to convert". Next, click "Save settings". This will save settings, as well as update the *.htaccess*.
49
 
50
+ If you are working in a browser that supports webp (ie Google Chrome), you will see a link "Convert test image (show debug)" after a successful save. Click that to test if it works. The screen should show a textual report of the conversion process. If it shows an image, it means that the *.htaccess* redirection isn't working. It may be that your server just needs some time. Some servers has set up caching.
51
 
52
  Note that the plugin does not change any HTML. In the HTML the image src is still set to ie "example.jpg". To verify that the plugin is working (without clicking the test button), do the following:
53
 
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.
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
 
78
  * The plugin does not work on Microsoft IIS server
79
  * The plugin has not been tested with multisite installation (it is on the roadmap!).
80
+ * There might be compatability issues with other plugins. For example .htaccess rules from other plugins might interfere. Please report if you discover any problems!
 
 
81
 
82
  == Frequently Asked Questions ==
83
 
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
 
133
  * Share converter with other sites. Optionally provide conversion service for other sites, which will be able to connect to it using the "wpc" converter.
134
+ * Support multisite setups
 
 
changelog.txt CHANGED
@@ -1,3 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
1
  = 0.3.1 =
2
  * The "Only jpeg" setting wasn't respected in 0.3.0. It now works again
3
 
1
+ = 0.4.0 =
2
+ This version fixes some misbehaviours and provides new http headers with info about the conversion process.
3
+
4
+ * Fixed bug: .htaccess was not updated every time the settings was saved.
5
+ * Fixed bug: The plugin generated error upon activation.
6
+ * Now produces X-WebP-Convert-And-Serve headers with info about the conversion - useful for validating that converter receives the expected arguments and executes correctly.
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 =
13
  * The "Only jpeg" setting wasn't respected in 0.3.0. It now works again
14
 
lib/activate-first-time.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/classes/Actions.php';
4
+ use \WebPExpress\Actions;
5
+
6
+ include_once __DIR__ . '/classes/Config.php';
7
+ use \WebPExpress\Config;
8
+
9
+ include_once __DIR__ . '/classes/Messenger.php';
10
+ use \WebPExpress\Messenger;
11
+
12
+ include_once __DIR__ . '/classes/PlatformInfo.php';
13
+ use \WebPExpress\PlatformInfo;
14
+
15
+ include_once __DIR__ . '/classes/State.php';
16
+ use \WebPExpress\State;
17
+
18
+ // First check basic requirements.
19
+ // -------------------------------
20
+
21
+ if (PlatformInfo::isMicrosoftIis()) {
22
+ Messenger::addMessage('error', 'You are on Microsof IIS server. The plugin does not work on IIS (yet). The plugin has been <i>deactivated</i> again!');
23
+ Actions::procastinate('deactivate');
24
+
25
+ // Well, that was it.
26
+ return;
27
+ }
28
+
29
+
30
+ if ( is_multisite() ) {
31
+ Messenger::addMessage('error', 'You are on multisite. It is not supported yet. BUT IT IS ON THE ROADMAP! Stay tuned! The plugin has been <i>deactivated</i> again!');
32
+ Actions::procastinate('deactivate');
33
+ return;
34
+ }
35
+
36
+ if (!version_compare(PHP_VERSION, '5.5.0', '>=')) {
37
+ //$msg = sprintf(__( 'You are on a very old version of PHP (%s). WebP Express may not work as intended.', 'webp-express' ), phpversion());
38
+ Messenger::addMessage(
39
+ 'warning',
40
+ 'You are on a very old version of PHP. WebP Express may not work correctly. Your PHP version:' . phpversion()
41
+ );
42
+ return;
43
+ }
44
+
45
+ // Next issue warnings, if any
46
+ // -------------------------------
47
+
48
+ if (PlatformInfo::isApache() || PlatformInfo::isLiteSpeed()) {
49
+ // all is well.
50
+ } else {
51
+ Messenger::addMessage(
52
+ 'warning',
53
+ 'You are not on Apache server, nor on LiteSpeed. WebP Express only works out of the box on Apache and LiteSpeed.<br>' .
54
+ 'But you may get it to work. WebP Express will print you rewrite rules for Apache. You could try to configure your server to do similar routing.<br>' .
55
+ 'Btw: your server is: ' . $_SERVER['SERVER_SOFTWARE']
56
+ );
57
+ }
58
+
59
+ // Welcome!
60
+ // -------------------------------
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
+ );
lib/activate-hook.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/classes/State.php';
4
+ use \WebPExpress\State;
5
+
6
+ // Test if plugin is activated for the first time, or simply reactivated
7
+ if (State::getState('configured', false)) {
8
+ include __DIR__ . "/reactivate.php";
9
+ } else {
10
+ include __DIR__ . "/activate-first-time.php";
11
+ }
lib/activate.php DELETED
@@ -1,88 +0,0 @@
1
- <?php
2
-
3
- include_once( plugin_dir_path( __FILE__ ) . 'helpers.php');
4
-
5
- class WebPExpressActivate {
6
-
7
-
8
- public static function activate() {
9
-
10
- update_option( 'webp-express-message-pending', true, false );
11
-
12
- update_option( 'webp-express-just-activated', true, false );
13
-
14
-
15
- $server = strtolower($_SERVER['SERVER_SOFTWARE']);
16
-
17
- $server_is_microsoft_iis = ( strpos( $server, 'microsoft-iis') !== false );
18
- if ($server_is_microsoft_iis) {
19
- update_option( 'webp-express-microsoft-iis', true, false );
20
- update_option( 'webp-express-deactivate', true, false );
21
- return;
22
- }
23
-
24
-
25
- $server_is_litespeed = ( strpos( $server, 'litespeed') !== false );
26
- $server_is_apache = ( strpos( $server, 'apache') !== false );
27
-
28
- if ($server_is_litespeed || $server_is_apache) {
29
- // all is well.
30
- } else {
31
- update_option( 'webp-express-not-apache-nor-litespeed', true, false );
32
- }
33
-
34
-
35
- if ( is_multisite() ) {
36
- update_option( 'webp-express-no-multisite', true, false );
37
- update_option( 'webp-express-deactivate', true, false );
38
- return;
39
- }
40
-
41
- if (!version_compare(PHP_VERSION, '5.5.0', '>=')) {
42
- update_option( 'webp-express-php-too-old', true, false );
43
- //update_option( 'webp-express-deactivate', true, false );
44
- return;
45
- }
46
-
47
-
48
- // Create upload dir
49
- $urlsAndPaths = WebPExpressHelpers::calculateUrlsAndPaths();
50
- $ourUploadDir = $urlsAndPaths['filePaths']['destinationRoot'];
51
-
52
- if ( ! file_exists( $ourUploadDir ) ) {
53
- wp_mkdir_p( $ourUploadDir );
54
- }
55
- if ( ! file_exists( $ourUploadDir ) ) {
56
- update_option( 'webp-express-failed-creating-upload-dir', true, false );
57
- update_option( 'webp-express-deactivate', true, false );
58
- return;
59
- }
60
-
61
- if (!empty(get_option('webp-express-configured'))) {
62
-
63
- // The plugin has been reactivated.
64
- // We must regenerate the .htaccess rules.
65
- $rules = WebPExpressHelpers::generateHTAccessRules();
66
- WebPExpressHelpers::insertHTAccessRules($rules);
67
-
68
- } else {
69
- // WebP Express has not been configured yet.
70
-
71
- // Should we perhaps write to .htaccess, in order to determine if there is a permission problem or not ?
72
- // like this:
73
- /*
74
- if (WebPExpressHelpers::doInsertHTAccessRules('# WebP Express has not been configured yet, so here are no rules yet.')) {
75
-
76
- } else {
77
- update_option('webp-express-failed-inserting-rules', true, false);
78
- }*/
79
-
80
-
81
- }
82
-
83
-
84
- }
85
-
86
- }
87
-
88
- WebPExpressActivate::activate();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/admin.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
8
+ include __DIR__ . '/migrate/migrate.php';
9
+ }
10
+
11
+ // uncomment next line to debug an error during activation
12
+ //include __DIR__ . "/debug.php";
13
+
14
+ include __DIR__ . '/options/options-hooks.php';
15
+
16
+ register_activation_hook(WEBPEXPRESS_PLUGIN, function () {
17
+ include __DIR__ . '/activate-hook.php';
18
+ });
19
+
20
+ register_deactivation_hook(WEBPEXPRESS_PLUGIN, function () {
21
+ include __DIR__ . '/deactivate.php';
22
+ });
23
+
24
+ if (get_option('webp-express-messages-pending')) {
25
+ include_once __DIR__ . '/classes/Messenger.php';
26
+ add_action( 'admin_notices', function() {
27
+ \WebPExpress\Messenger::printPendingMessages();
28
+ });
29
+ }
30
+ if (get_option('webp-express-actions-pending')) {
31
+ include_once __DIR__ . '/classes/Actions.php';
32
+ \WebPExpress\Actions::processQueuedActions();
33
+ }
34
+
35
+ function webp_express_uninstall() {
36
+ include __DIR__ . '/uninstall.php';
37
+ }
38
+
39
+ // interestingly, I get "Serialization of 'Closure' is not allowed" if I pass anonymous function
40
+ // ... perhaps we should not do that in the other hooks either.
41
+ register_uninstall_hook(WEBPEXPRESS_PLUGIN, 'webp_express_uninstall');
42
+
43
+ // Add settings link on the plugins page
44
+ add_filter('plugin_action_links_' . plugin_basename(WEBPEXPRESS_PLUGIN), function ( $links ) {
45
+ $mylinks = array(
46
+ '<a href="' . admin_url( 'options-general.php?page=webp_express_settings_page' ) . '">Settings</a>',
47
+ );
48
+ return array_merge( $links, $mylinks );
49
+ });
lib/classes/Actions.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "State.php";
6
+ use \WebPExpress\State;
7
+
8
+ /**
9
+ *
10
+ */
11
+
12
+ class Actions
13
+ {
14
+ /**
15
+ * $action: identifier
16
+ */
17
+ public static function procastinate($action) {
18
+ update_option('webp-express-actions-pending', true, true);
19
+
20
+ $pendingActions = State::getState('pendingActions', []);
21
+ $pendingActions[] = $action;
22
+ State::setState('pendingActions', $pendingActions);
23
+ }
24
+
25
+ public static function takeAction($action) {
26
+ switch ($action) {
27
+ case 'deactivate':
28
+ add_action('admin_init', function () {
29
+ deactivate_plugins(plugin_basename(WEBPEXPRESS_PLUGIN));
30
+ });
31
+ break;
32
+ }
33
+ }
34
+
35
+ public static function processQueuedActions() {
36
+ $actions = State::getState('pendingActions', []);
37
+
38
+ foreach ($actions as $action) {
39
+ self::takeAction($action);
40
+ }
41
+
42
+ State::setState('pendingActions', []);
43
+ update_option('webp-express-actions-pending', false, true);
44
+
45
+ }
46
+ }
lib/classes/Config.php ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "FileHelper.php";
6
+ use \WebPExpress\FileHelper;
7
+
8
+ include_once "HTAccess.php";
9
+ use \WebPExpress\HTAccess;
10
+
11
+ include_once "Messenger.php";
12
+ use \WebPExpress\Messenger;
13
+
14
+ include_once "Paths.php";
15
+ use \WebPExpress\Paths;
16
+
17
+ include_once "State.php";
18
+ use \WebPExpress\State;
19
+
20
+ class Config
21
+ {
22
+
23
+ /**
24
+ * Return object or false, if config file does not exist, or read error
25
+ */
26
+ public static function loadJSONOptions($filename)
27
+ {
28
+ $json = FileHelper::loadFile($filename);
29
+ if ($json === false) {
30
+ return false;
31
+ }
32
+
33
+ $options = json_decode($json, true);
34
+ if ($options === null) {
35
+ return false;
36
+ }
37
+ return $options;
38
+ }
39
+
40
+ public static function saveJSONOptions($filename, $obj)
41
+ {
42
+ $result = @file_put_contents($filename, json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK));
43
+ /*if ($result === false) {
44
+ echo 'COULD NOT' . $filename;
45
+ }*/
46
+ return ($result !== false);
47
+ }
48
+
49
+
50
+ public static function loadConfig()
51
+ {
52
+ return self::loadJSONOptions(Paths::getConfigFileName());
53
+ }
54
+
55
+ public static function isConfigFileThere()
56
+ {
57
+ return (FileHelper::fileExists(Paths::getConfigFileName()));
58
+ }
59
+
60
+ public static function isConfigFileThereAndOk()
61
+ {
62
+ return (self::loadConfig() !== false);
63
+ }
64
+
65
+ public static function loadWodOptions()
66
+ {
67
+ return self::loadJSONOptions(Paths::getWodOptionsFileName());
68
+ }
69
+
70
+ public static function saveConfigurationFile($config)
71
+ {
72
+ $config['paths-used-in-htaccess'] = [
73
+ 'existing' => Paths::getPathToExisting(),
74
+ 'wod-url-path' => Paths::getWodUrlPath(),
75
+ 'config-dir-rel' => Paths::getConfigDirRel()
76
+ ];
77
+
78
+ if (Paths::createConfigDirIfMissing()) {
79
+ $success = self::saveJSONOptions(Paths::getConfigFileName(), $config);
80
+ if ($success) {
81
+ State::setState('configured', true);
82
+ }
83
+ return $success;
84
+ }
85
+ return false;
86
+ }
87
+
88
+ public static function generateWodOptionsFromConfigObj($config)
89
+ {
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
+
108
+ public static function saveWodOptionsFile($options)
109
+ {
110
+ if (Paths::createConfigDirIfMissing()) {
111
+ return self::saveJSONOptions(Paths::getWodOptionsFileName(), $options);
112
+ }
113
+ return false;
114
+ }
115
+
116
+ /**
117
+ *
118
+ * $rewriteRulesNeedsUpdate:
119
+ */
120
+ public static function saveConfigurationAndHTAccess($config, $forceRuleUpdating = false)
121
+ {
122
+ // Important to do this check before saving config, because the method
123
+ // compares against existing config.
124
+
125
+ if ($forceRuleUpdating) {
126
+ $rewriteRulesNeedsUpdate = true;
127
+ } else {
128
+ $rewriteRulesNeedsUpdate = HTAccess::doesRewriteRulesNeedUpdate($config);
129
+ }
130
+
131
+ if (self::saveConfigurationFile($config)) {
132
+ $options = self::generateWodOptionsFromConfigObj($config);
133
+ if (self::saveWodOptionsFile($options)) {
134
+ if ($rewriteRulesNeedsUpdate) {
135
+ $rulesResult = HTAccess::saveRules($config);
136
+ return [
137
+ 'saved-both-config' => true,
138
+ 'saved-main-config' => true,
139
+ 'rules-needed-update' => true,
140
+ 'htaccess-result' => $rulesResult
141
+ ];
142
+ }
143
+ else {
144
+ $rulesResult = HTAccess::saveRules($config);
145
+ return [
146
+ 'saved-both-config' => true,
147
+ 'saved-main-config' => true,
148
+ 'rules-needed-update' => false,
149
+ 'htaccess-result' => $rulesResult
150
+ ];
151
+ }
152
+ } else {
153
+ return [
154
+ 'saved-both-config' => false,
155
+ 'saved-main-config' => true,
156
+ ];
157
+ }
158
+ } else {
159
+ return [
160
+ 'saved-both-config' => false,
161
+ 'saved-main-config' => false,
162
+ ];
163
+ }
164
+ }
165
+ }
lib/classes/FileHelper.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class FileHelper
6
+ {
7
+
8
+ public static function fileExists($filename) {
9
+ return @file_exists($filename);
10
+ }
11
+
12
+ /**
13
+ * Get file permission of a file (integer). Only get the last part, ie 0644
14
+ * If failure, it returns false
15
+ */
16
+ public static function filePerm($filename) {
17
+ if (!self::fileExists($filename)) {
18
+ return false;
19
+ }
20
+
21
+ // fileperms can still fail. In that case, it returns false
22
+ $perm = @fileperms($filename);
23
+ if ($perm === false) {
24
+ return false;
25
+ }
26
+
27
+ return octdec(substr(decoct($perm), -4));
28
+ }
29
+
30
+ public static function humanReadableFilePerm($mode) {
31
+ return substr(decoct($mode), -4);
32
+ }
33
+
34
+ public static function humanReadableFilePermOfFile($filename) {
35
+ return self::readableFilePerm(self::filePerm($filename));
36
+ }
37
+
38
+ /**
39
+ * As the return value of the PHP function isn't reliable,
40
+ * we have our own chmod.
41
+ */
42
+ public static function chmod($filename, $mode) {
43
+ // In case someone carelessly passed the result of a filePerm call, which was false:
44
+ if ($mode === false) {
45
+ return false;
46
+ }
47
+ $existingPermission = self::filePerm($filename);
48
+ if ($mode === $existingPermission) {
49
+ return true;
50
+ }
51
+ if (@chmod($filename, $mode)) {
52
+ // in some cases chmod returns true, even though it did not succeed!
53
+ // - so we test if our operation had the desired effect.
54
+ if (self::filePerm($filename) !== $mode) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ /**
63
+ * Get directory part of filename.
64
+ * Ie '/var/www/.htaccess' => '/var/www'
65
+ * Also works with backslashes
66
+ */
67
+ public static function dirName($filename) {
68
+ return preg_replace('/[\/\\\\][^\/\\\\]*$/', '', $filename);
69
+ }
70
+
71
+ /**
72
+ * Determines if a file can be created.
73
+ * BEWARE: It requires that the containing folder already exists
74
+ */
75
+ public static function canCreateFile($filename) {
76
+ $dirName = self::dirName($filename);
77
+ if (!@file_exists($dirName)) {
78
+ return false;
79
+ }
80
+ if (@is_writable($dirName) && @is_executable($dirName)) {
81
+ return true;
82
+ }
83
+
84
+ $existingPermission = self::filePerm($dirName);
85
+
86
+ // we need to make sure we got the existing permission, so we can revert correctly later
87
+ if ($existingPermission !== false) {
88
+ if (self::chmod($dirName, 0775)) {
89
+ // change back
90
+ self::chmod($filename, $existingPermission);
91
+ return true;
92
+ }
93
+ }
94
+ return false;
95
+ }
96
+
97
+ /**
98
+ * Note: Do not use for directories
99
+ */
100
+ public static function canEditFile($filename) {
101
+ if (!@file_exists($filename)) {
102
+ return false;
103
+ }
104
+ if (@is_writable($filename) && @is_readable($filename)) {
105
+ return true;
106
+ }
107
+
108
+ // As a last desperate try, lets see if we can give ourself write permissions.
109
+ // If possible, then it will also be possible when actually writing
110
+ $existingPermission = self::filePerm($filename);
111
+
112
+ // we need to make sure we got the existing permission, so we can revert correctly later
113
+ if ($existingPermission !== false) {
114
+ if (self::chmod($filename, 0664)) {
115
+ // change back
116
+ self::chmod($filename, $existingPermission);
117
+ return true;
118
+ }
119
+ }
120
+ return false;
121
+
122
+ // Idea: Perhaps we should also try to actually open the file for writing?
123
+
124
+ }
125
+
126
+ public static function canEditOrCreateFileHere($filename) {
127
+ if (@file_exists($filename)) {
128
+ return self::canEditFile($filename);
129
+ } else {
130
+ return self::canCreateFile($filename);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Try to read from a file. Tries hard.
136
+ * Returns content, or false if read error.
137
+ */
138
+ public static function loadFile($filename) {
139
+ $changedPermission = false;
140
+ if (!@is_readable($filename)) {
141
+ $existingPermission = self::filePerm($filename);
142
+
143
+ // we need to make sure we got the existing permission, so we can revert correctly later
144
+ if ($existingPermission !== false) {
145
+ $changedPermission = self::chmod($filename, 0664);
146
+ }
147
+ }
148
+
149
+ $return = false;
150
+ $handle = @fopen($filename, "r");
151
+ if ($handle !== false) {
152
+ // Return value is either file content or false
153
+ $return = @fread($handle, filesize($filename));
154
+ fclose($handle);
155
+ }
156
+
157
+ if ($changedPermission) {
158
+ // change back
159
+ self::chmod($filename, $existingPermission);
160
+ }
161
+ return $return;
162
+ }
163
+ }
lib/classes/HTAccess.php ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "Config.php";
6
+ use \WebPExpress\Config;
7
+
8
+ include_once "FileHelper.php";
9
+ use \WebPExpress\FileHelper;
10
+
11
+ include_once "Paths.php";
12
+ use \WebPExpress\Paths;
13
+
14
+ include_once "State.php";
15
+ use \WebPExpress\State;
16
+
17
+ class HTAccess
18
+ {
19
+
20
+ public static function generateHTAccessRulesFromConfigObj($config)
21
+ {
22
+
23
+ /* Calculate $fileExt */
24
+ $imageTypes = $config['image-types'];
25
+ $fileExtensions = [];
26
+ if ($imageTypes & 1) {
27
+ $fileExtensions[] = 'jpe?g';
28
+ }
29
+ if ($imageTypes & 2) {
30
+ $fileExtensions[] = 'png';
31
+ }
32
+ $fileExt = implode('|', $fileExtensions);
33
+
34
+ if ($imageTypes == 0) {
35
+ return '# WebP Express disabled (no image types have been choosen to be converted)';
36
+ }
37
+ /* Build rules */
38
+ $rules = '';
39
+
40
+ // The next line sets an environment variable.
41
+ // On the options page, we verify if this is set to diagnose if "AllowOverride None" is presented in 'httpd.conf'
42
+ //$rules .= "# The following SetEnv allows to diagnose if .htaccess files are turned off\n";
43
+ //$rules .= "SetEnv HTACCESS on\n\n";
44
+
45
+ $rules .= "<IfModule mod_rewrite.c>\n" .
46
+ " RewriteEngine On\n\n";
47
+
48
+ $pathToExisting = Paths::getPathToExisting();
49
+
50
+ /*
51
+ // TODO: handle when wp-content is outside document root.
52
+ // TODO: this should be made optional
53
+ if (true) {
54
+ # Redirect to existing converted image (under appropriate circumstances)
55
+ $rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
56
+ $rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $pathToExisting . "/$1.$2.webp -f\n";
57
+ $rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $pathToExisting . "/$1.$2.webp [NC,T=image/webp,QSD,L]\n\n";
58
+ }*/
59
+
60
+
61
+ $rules .= " # Redirect images to webp-on-demand.php (if browser supports webp)\n";
62
+ $rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
63
+ if ($config['forward-query-string']) {
64
+ $rules .= " RewriteCond %{QUERY_STRING} (.*)\n";
65
+ }
66
+ $rules .= " RewriteRule ^(.*)\.(" . $fileExt . ")$ " .
67
+ "/" . Paths::getWodUrlPath() .
68
+ "?source=%{SCRIPT_FILENAME}" .
69
+ "&wp-content=" . Paths::getWPContentDirRel() .
70
+ ($config['forward-query-string'] ? '&%1' : '') .
71
+ " [NC,L]\n";
72
+
73
+ $rules .="</IfModule>\n" .
74
+ "AddType image/webp .webp\n";
75
+
76
+ return $rules;
77
+ }
78
+
79
+ public static function generateHTAccessRulesFromConfigFile() {
80
+ if (Config::isConfigFileThereAndOk()) {
81
+ return self::generateHTAccessRulesFromConfigObj(Config::loadConfig());
82
+ } else {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ public static function arePathsUsedInHTAccessOutdated() {
88
+ if (!Config::isConfigFileThere()) {
89
+ // this properly means that rewrite rules have never been generated
90
+ return false;
91
+ }
92
+
93
+ $pathsGoingToBeUsedInHtaccess = [
94
+ 'existing' => Paths::getPathToExisting(),
95
+ 'wod-url-path' => Paths::getWodUrlPath(),
96
+ 'config-dir-rel' => Paths::getConfigDirRel()
97
+ ];
98
+
99
+ $config = Config::loadConfig();
100
+ if ($config === false) {
101
+ // corrupt or not readable
102
+ return true;
103
+ }
104
+
105
+ foreach ($config['paths-used-in-htaccess'] as $prop => $value) {
106
+ if ($value != $pathsGoingToBeUsedInHtaccess[$prop]) {
107
+ return true;
108
+ }
109
+ }
110
+ }
111
+
112
+ public static function doesRewriteRulesNeedUpdate($newConfig) {
113
+ if (!Config::isConfigFileThere()) {
114
+ // this properly means that rewrite rules have never been generated
115
+ return true;
116
+ }
117
+
118
+ $oldConfig = Config::loadConfig();
119
+ if ($oldConfig === false) {
120
+ // corrupt or not readable
121
+ return true;
122
+ }
123
+
124
+ $propsToCompare = ['forward-query-string', 'image-types'];
125
+
126
+
127
+ foreach ($propsToCompare as $prop) {
128
+ if ($newConfig[$prop] != $oldConfig[$prop]) {
129
+ return true;
130
+ }
131
+ }
132
+
133
+ if (!isset($oldConfig['paths-used-in-htaccess'])) {
134
+ return true;
135
+ }
136
+
137
+ return self::arePathsUsedInHTAccessOutdated();
138
+ }
139
+
140
+ /**
141
+ * Must be parsed ie "wp-content", "index", etc. Not real dirs
142
+ */
143
+ public static function addToActiveHTAccessDirsArray($whichDir)
144
+ {
145
+ $activeHtaccessDirs = State::getState('active-htaccess-dirs', []);
146
+ if (!in_array($whichDir, $activeHtaccessDirs)) {
147
+ $activeHtaccessDirs[] = $whichDir;
148
+ State::setState('active-htaccess-dirs', array_values($activeHtaccessDirs));
149
+ }
150
+ }
151
+
152
+ public static function removeFromActiveHTAccessDirsArray($whichDir)
153
+ {
154
+ $activeHtaccessDirs = State::getState('active-htaccess-dirs', []);
155
+ if (in_array($whichDir, $activeHtaccessDirs)) {
156
+ $activeHtaccessDirs = array_diff($activeHtaccessDirs, [$whichDir]);
157
+ State::setState('active-htaccess-dirs', array_values($activeHtaccessDirs));
158
+ }
159
+ }
160
+
161
+ public static function isInActiveHTAccessDirsArray($whichDir)
162
+ {
163
+ $activeHtaccessDirs = State::getState('active-htaccess-dirs', []);
164
+ return (in_array($whichDir, $activeHtaccessDirs));
165
+ }
166
+
167
+ public static function whichHTAccessDirIsThis($dir) {
168
+ switch ($dir) {
169
+ case Paths::getWPContentDirAbs():
170
+ return 'wp-content';
171
+ case Paths::getIndexDirAbs():
172
+ return 'index';
173
+ case Paths::getHomeDirAbs():
174
+ return 'home';
175
+ case Paths::getPluginDirAbs():
176
+ return 'plugins';
177
+ case Paths::getUploadDirAbs():
178
+ return 'uploads';
179
+ }
180
+ return '';
181
+ }
182
+
183
+ public static function hasRecordOfSavingHTAccessToDir($dir) {
184
+ $whichDir = self::whichHTAccessDirIsThis($dir);
185
+ if ($whichDir != '') {
186
+ return self::isInActiveHTAccessDirsArray($whichDir);
187
+ }
188
+ return false;
189
+ }
190
+
191
+
192
+ /**
193
+ * Sneak peak into .htaccess to see if we have rules in it
194
+ * This may not be possible.
195
+ * Return true, false, or null if we just can't tell
196
+ */
197
+ public static function haveWeRulesInThisHTAccess($filename) {
198
+ if (FileHelper::fileExists($filename)) {
199
+ $content = FileHelper::loadFile($filename);
200
+ if ($content === false) {
201
+ return null;
202
+ }
203
+ return (strpos($content, '# Redirect images to webp-on-demand.php') != false);
204
+ } else {
205
+ // the .htaccess isn't even there. So there are no rules.
206
+ return false;
207
+ }
208
+ }
209
+
210
+ public static function haveWeRulesInThisHTAccessBestGuess($filename)
211
+ {
212
+ // First try to sneak peak. May return null if it cannot be determined.
213
+ $result = self::haveWeRulesInThisHTAccess($filename);
214
+ if ($result === true) {
215
+ return true;
216
+ }
217
+ if ($result === null) {
218
+ // We were not allowed to sneak-peak.
219
+ // Well, good thing that we stored successful .htaccess write locations ;)
220
+ // If we recorded a successful write, then we assume there are still rules there
221
+ $dir = FileHelper::dirName($filename);
222
+ return self::hasRecordOfSavingHTAccessToDir($dir);
223
+ }
224
+ }
225
+
226
+ public static function saveHTAccessRulesToFile($filename, $rules, $createIfMissing = false) {
227
+ if (!@file_exists($filename)) {
228
+ if (!$createIfMissing) {
229
+ return false;
230
+ }
231
+ // insert_with_markers will create file if it doesn't exist, so we can continue...
232
+ }
233
+
234
+ $existingFilePermission = null;
235
+ $existingDirPermission = null;
236
+
237
+ // Try to make .htaccess writable if its not
238
+ if (@file_exists($filename)) {
239
+ if (!@is_writable($filename)) {
240
+ $existingFilePermission = FileHelper::filePerm($filename);
241
+ @chmod($filename, 0664); // chmod may fail, we know...
242
+ }
243
+ } else {
244
+ $dir = FileHelper::dirName($filename);
245
+ if (!@is_writable($dir)) {
246
+ $existingDirPermission = FileHelper::filePerm($dir);
247
+ @chmod($dir, 0775);
248
+ }
249
+ }
250
+
251
+ /* Add rules to .htaccess */
252
+ if (!function_exists('insert_with_markers')) {
253
+ require_once ABSPATH . 'wp-admin/includes/misc.php';
254
+ }
255
+
256
+ // Convert to array, because string version has bugs in Wordpress 4.3
257
+ $rules = explode("\n", $rules);
258
+ $success = insert_with_markers($filename, 'WebP Express', $rules);
259
+
260
+ // Revert file or dir permissions
261
+ if (!is_null($existingFilePermission)) {
262
+ @chmod($filename, $existingFilePermission);
263
+ }
264
+ if (!is_null($existingDirPermission)) {
265
+ @chmod($dir, $existingDirPermission);
266
+ }
267
+
268
+ if ($success) {
269
+ State::setState('htaccess-rules-saved-at-some-point', true);
270
+
271
+ $containsRules = (strpos(implode('',$rules), '# Redirect images to webp-on-demand.php') != false);
272
+
273
+ $dir = FileHelper::dirName($filename);
274
+ $whichDir = self::whichHTAccessDirIsThis($dir);
275
+ if ($whichDir != '') {
276
+ if ($containsRules) {
277
+ self::addToActiveHTAccessDirsArray($whichDir);
278
+ } else {
279
+ self::removeFromActiveHTAccessDirsArray($whichDir);
280
+ }
281
+ }
282
+ }
283
+
284
+ return $success;
285
+ }
286
+
287
+ public static function saveHTAccessRulesToFirstWritableHTAccessDir($dirs, $rules)
288
+ {
289
+ foreach ($dirs as $dir) {
290
+ if (self::saveHTAccessRulesToFile($dir . '/.htaccess', $rules, true)) {
291
+ return $dir;
292
+ }
293
+ }
294
+ return false;
295
+ }
296
+
297
+
298
+ /**
299
+ * Try to deactivate all .htaccess rules.
300
+ * If success, we return true.
301
+ * If we fail, we return an array of filenames that have problems
302
+ */
303
+ public static function deactivateHTAccessRules() {
304
+ //return self::saveHTAccessRules('# Plugin is deactivated');
305
+ $indexDir = Paths::getIndexDirAbs();
306
+ $homeDir = Paths::getHomeDirAbs();
307
+ $wpContentDir = Paths::getWPContentDirAbs();
308
+ $pluginDir = Paths::getPluginDirAbs();
309
+ $uploadDir = Paths::getUploadDirAbs();
310
+
311
+ $dirsToClean = [$indexDir, $homeDir, $wpContentDir, $pluginDir, $uploadDir];
312
+
313
+ $failures = [];
314
+
315
+ foreach ($dirsToClean as $dir) {
316
+ $filename = $dir . '/.htaccess';
317
+ if (!FileHelper::fileExists($filename)) {
318
+ continue;
319
+ } else {
320
+ if (self::haveWeRulesInThisHTAccessBestGuess($filename)) {
321
+ if (!self::saveHTAccessRulesToFile($filename, '# Plugin is deactivated', false)) {
322
+ $failures[] = $filename;
323
+ }
324
+ }
325
+ }
326
+ }
327
+ if (count($failures) == 0) {
328
+ return true;
329
+ }
330
+ return $failures;
331
+ }
332
+
333
+ public static function testLinks($config) {
334
+ if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
335
+ if ($config['image-types'] != 0) {
336
+ $webpExpressRoot = Paths::getPluginUrlPath();
337
+ return '<br>' .
338
+ '<a href="/' . $webpExpressRoot . '/test/test.jpg?debug&time=' . time() . '" target="_blank">Convert test image (show debug)</a><br>' .
339
+ '<a href="/' . $webpExpressRoot . '/test/test.jpg?' . time() . '" target="_blank">Convert test image</a><br>';
340
+ }
341
+ }
342
+ return '';
343
+ }
344
+
345
+
346
+ public static function getHTAccessDirRequirements() {
347
+ $minRequired = 'index';
348
+ if (Paths::isWPContentDirMovedOutOfAbsPath()) {
349
+ $minRequired = 'wp-content';
350
+ $pluginToo = Paths::isPluginDirMovedOutOfWpContent() ? 'yes' : 'no';
351
+ $uploadToo = Paths::isUploadDirMovedOutOfWPContentDir() ? 'yes' : 'no';
352
+ } else {
353
+ // plugin requirement depends...
354
+ // - if user grants access to 'index', the requirement is Paths::isPluginDirMovedOutOfAbsPath()
355
+ // - if user grants access to 'wp-content', the requirement is Paths::isPluginDirMovedOutOfWpContent()
356
+ $pluginToo = 'depends';
357
+
358
+ // plugin requirement depends...
359
+ // - if user grants access to 'index', we should be fine, as UPLOADS is always in ABSPATH.
360
+ // - if user grants access to 'wp-content', the requirement is Paths::isUploadDirMovedOutOfWPContentDir()
361
+ $uploadToo = 'depends';
362
+ }
363
+
364
+ return [
365
+ $minRequired,
366
+ $pluginToo, // 'yes', 'no' or 'depends'
367
+ $uploadToo
368
+ ];
369
+ }
370
+
371
+ /**
372
+ * Try to save the rules.
373
+ * Returns many details
374
+ */
375
+ public static function saveRules($config) {
376
+
377
+ $rules = HTAccess::generateHTAccessRulesFromConfigObj($config);
378
+
379
+ list($minRequired, $pluginToo, $uploadToo) = self::getHTAccessDirRequirements();
380
+
381
+ $indexDir = Paths::getIndexDirAbs();
382
+ $wpContentDir = Paths::getWPContentDirAbs();
383
+
384
+ $acceptableDirs = [
385
+ $wpContentDir
386
+ ];
387
+ if ($minRequired == 'index') {
388
+ $acceptableDirs[] = $indexDir;
389
+ }
390
+
391
+ $overidingRulesInWpContentWarning = false;
392
+ $result = HTAccess::saveHTAccessRulesToFirstWritableHTAccessDir($acceptableDirs, $rules);
393
+ if ($result == $wpContentDir) {
394
+ $mainResult = 'wp-content';
395
+ //if (self::haveWeRulesInThisHTAccessBestGuess($indexDir . '/.htaccess')) {
396
+ HTAccess::saveHTAccessRulesToFile($indexDir . '/.htaccess', '# WebP Express has placed its rules in your wp-content dir. Go there.', false);
397
+ //}
398
+ } elseif ($result == $indexDir) {
399
+ $mainResult = 'index';
400
+ $overidingRulesInWpContentWarning = self::haveWeRulesInThisHTAccessBestGuess($wpContentDir . '/.htaccess');
401
+ } elseif ($result === false) {
402
+ $mainResult = 'failed';
403
+ }
404
+
405
+ /* plugin */
406
+ if ($pluginToo == 'depends') {
407
+ if ($mainResult == 'wp-content') {
408
+ $pluginToo = (Paths::isPluginDirMovedOutOfWpContent() ? 'yes' : 'no');
409
+ } elseif ($mainResult == 'index') {
410
+ $pluginToo = (Paths::isPluginDirMovedOutOfAbsPath() ? 'yes' : 'no');
411
+ } else {
412
+ // $result must be false. So $pluginToo should still be 'depends'
413
+ }
414
+ }
415
+ $pluginFailed = false;
416
+ $pluginFailedBadly = true;
417
+ if ($pluginToo == 'yes') {
418
+ $pluginDir = Paths::getPluginDirAbs();
419
+ $pluginFailed = !(HTAccess::saveHTAccessRulesToFile($pluginDir . '/.htaccess', $rules, true));
420
+ if ($pluginFailed) {
421
+ $pluginFailedBadly = self::haveWeRulesInThisHTAccessBestGuess($pluginDir . '/.htaccess');
422
+ }
423
+ }
424
+
425
+ /* upload */
426
+ if ($uploadToo == 'depends') {
427
+ if ($mainResult == 'wp-content') {
428
+ $uploadToo = (Paths::isUploadDirMovedOutOfWPContentDir() ? 'yes' : 'no');
429
+ } elseif ($mainResult == 'index') {
430
+ $uploadToo = (Paths::isUploadDirMovedOutOfAbsPath() ? 'yes' : 'no');
431
+ } else {
432
+ // $result must be false. So $uploadToo should still be 'depends'
433
+ }
434
+ }
435
+ $uploadFailed = false;
436
+ $uploadFailedBadly = true;
437
+ if ($uploadToo == 'yes') {
438
+ $uploadDir = Paths::getUploadDirAbs();
439
+ $uploadFailed = !(HTAccess::saveHTAccessRulesToFile($uploadDir . '/.htaccess', $rules, true));
440
+ if ($uploadFailed) {
441
+ $uploadFailedBadly = self::haveWeRulesInThisHTAccessBestGuess($uploadDir . '/.htaccess');
442
+ }
443
+ }
444
+
445
+ return [
446
+ 'mainResult' => $mainResult, // 'index', 'wp-content' or 'failed'
447
+ 'minRequired' => $minRequired, // 'index' or 'wp-content'
448
+ 'overidingRulesInWpContentWarning' => $overidingRulesInWpContentWarning, // true if main result is 'index' but we cannot remove those in wp-content
449
+ 'rules' => $rules, // The rules we generated
450
+ 'pluginToo' => $pluginToo, // 'yes', 'no' or 'depends'
451
+ 'pluginFailed' => $pluginFailed, // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
452
+ 'pluginFailedBadly' => $pluginFailedBadly, // true if plugin failed AND it seems we have rewrite rules there
453
+ 'uploadToo' => $uploadToo, // 'yes', 'no' or 'depends'
454
+ 'uploadFailed' => $uploadFailed,
455
+ 'uploadFailedBadly' => $uploadFailedBadly,
456
+ ];
457
+ }
458
+ }
lib/classes/Messenger.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "State.php";
6
+ use \WebPExpress\State;
7
+
8
+ class Messenger
9
+ {
10
+ private static $printedStyles = false;
11
+
12
+ /**
13
+ * $level: info | success | warning | error
14
+ * $msg: the message (not translated)
15
+ *
16
+ * Hm... we should add some sprintf-like support
17
+ * $msg = sprintf(__( 'You are on a very old version of PHP (%s). WebP Express may not work as intended.', 'webp-express' ), phpversion());
18
+ */
19
+ public static function addMessage($level, $msg) {
20
+
21
+ update_option('webp-express-messages-pending', true, true); // We want this option to be autoloaded
22
+
23
+ $pendingMessages = State::getState('pendingMessages', []);
24
+ $pendingMessages[] = ['level' => $level, 'message' => $msg];
25
+ State::setState('pendingMessages', $pendingMessages);
26
+ }
27
+
28
+ public static function printMessage($level, $msg) {
29
+ if (!(self::$printedStyles)) {
30
+ global $wp_version;
31
+ if (floatval(substr($wp_version, 0, 3)) < 4.1) {
32
+ // Actually, I don't know precisely what version the styles were introduced.
33
+ // They are there in 4.1. They are not there in 4.0
34
+ self::printMessageStylesForOldWordpress();
35
+ }
36
+ self::$printedStyles = true;
37
+ }
38
+
39
+ //$msg = __( $msg, 'webp-express'); // uncommented. We should add some sprintf-like functionality before making the plugin translatable
40
+ printf(
41
+ '<div class="%1$s"><p>%2$s</p></div>',
42
+ esc_attr('notice notice-' . $level . ' is-dismissible'),
43
+ $msg
44
+ );
45
+ }
46
+
47
+ private static function printMessageStylesForOldWordpress() {
48
+ ?>
49
+ <style>
50
+ /* In Older Wordpress (ie 4.0), .notice is not declared */
51
+ .notice {
52
+ background: #fff;
53
+ border-left: 4px solid #fff;
54
+ -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
55
+ box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
56
+ margin: 10px 15px 2px 2px;
57
+ padding: 1px 12px;
58
+ }
59
+ .notice-error {
60
+ border-left-color: #dc3232;
61
+ }
62
+ .notice-success {
63
+ border-left-color: #46b450;
64
+ }
65
+ .notice-info {
66
+ border-left-color: #00a0d2;
67
+ }
68
+ .notice-warning {
69
+ border-left-color: #ffb900;
70
+ }
71
+ </style>
72
+ <?php
73
+ }
74
+
75
+ public static function printPendingMessages() {
76
+
77
+
78
+ $messages = State::getState('pendingMessages', []);
79
+
80
+ foreach ($messages as $message) {
81
+ self::printMessage($message['level'], $message['message']);
82
+ }
83
+
84
+ State::setState('pendingMessages', []);
85
+ update_option('webp-express-messages-pending', false, true);
86
+ }
87
+ }
lib/classes/PathHelper.php ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class PathHelper
6
+ {
7
+
8
+ /**
9
+ * Replace double slash with single slash. ie '/var//www/' => '/var/www/'
10
+ * This allows you to lazely concatenate paths with '/' and then call this method to clean up afterwards.
11
+ * Also removes triple slash etc.
12
+ */
13
+ public static function fixDoubleSlash($str)
14
+ {
15
+ return preg_replace('/\/\/+/', '/', $str);
16
+ }
17
+
18
+ /**
19
+ * Remove trailing slash, if any
20
+ */
21
+ public static function untrailSlash($str)
22
+ {
23
+ return rtrim($str, '/');
24
+ //return preg_replace('/\/$/', '', $str);
25
+ }
26
+
27
+ // Canonicalize a path by resolving '../' and './'
28
+ // Got it from a comment here: http://php.net/manual/en/function.realpath.php
29
+ // But fixed it (it could not handle './../')
30
+ public static function canonicalize($path) {
31
+ $parts = explode('/', $path);
32
+
33
+ // Remove parts containing just '.' (and the empty holes afterwards)
34
+ $parts = array_values(array_filter($parts, function($var) {
35
+ return ($var != '.');
36
+ }));
37
+
38
+ // Remove parts containing '..' and the preceding
39
+ $keys = array_keys($parts, '..');
40
+ foreach($keys as $keypos => $key) {
41
+ array_splice($parts, $key - ($keypos * 2 + 1), 2);
42
+ }
43
+ return implode('/', $parts);
44
+ }
45
+
46
+ /**
47
+ * Returns absolute path from a relative path and root
48
+ * The result is canonicalized (dots and double-dots are resolved)
49
+ *
50
+ * @param $path Absolute path or relative path
51
+ * @param $root What the path is relative to, if its relative
52
+ */
53
+ public static function relPathToAbsPath($path, $root)
54
+ {
55
+ return self::canonicalize(self::fixDoubleSlash($root . '/' . $path));
56
+ }
57
+
58
+ /**
59
+ * isAbsPath
60
+ * If path starts with '/', it is considered an absolute path (no Windows support)
61
+ *
62
+ * @param $path Path to inspect
63
+ */
64
+ public static function isAbsPath($path)
65
+ {
66
+ return (substr($path, 0, 1) == '/');
67
+ }
68
+
69
+ /**
70
+ * Returns absolute path from a path which can either be absolute or relative to second argument.
71
+ * If path starts with '/', it is considered an absolute path.
72
+ * The result is canonicalized (dots and double-dots are resolved)
73
+ *
74
+ * @param $path Absolute path or relative path
75
+ * @param $root What the path is relative to, if its relative
76
+ */
77
+ public static function pathToAbsPath($path, $root)
78
+ {
79
+ if (self::isAbsPath($path)) {
80
+ // path is already absolute
81
+ return $path;
82
+ } else {
83
+ return self::relPathToAbsPath($path, $root);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get relative path between two absolute paths
89
+ * Examples:
90
+ * from '/var/www' to 'var/ddd'. Result: '../ddd'
91
+ * from '/var/www' to 'var/www/images'. Result: 'images'
92
+ * from '/var/www' to 'var/www'. Result: '.'
93
+ */
94
+ public static function getRelDir($fromPath, $toPath)
95
+ {
96
+ $fromDirParts = explode('/', str_replace('\\', '/', self::canonicalize(self::untrailSlash($fromPath))));
97
+ $toDirParts = explode('/', str_replace('\\', '/', self::canonicalize(self::untrailSlash($toPath))));
98
+ $i = 0;
99
+ while (($i < count($fromDirParts)) && ($i < count($toDirParts)) && ($fromDirParts[$i] == $toDirParts[$i])) {
100
+ $i++;
101
+ }
102
+ $rel = "";
103
+ for ($j = $i; $j < count($fromDirParts); $j++) {
104
+ $rel .= "../";
105
+ }
106
+
107
+ for ($j = $i; $j < count($toDirParts); $j++) {
108
+ $rel .= $toDirParts[$j];
109
+ if ($j < count($toDirParts)-1) {
110
+ $rel .= '/';
111
+ }
112
+ }
113
+ if ($rel == '') {
114
+ $rel = '.';
115
+ }
116
+ return $rel;
117
+ }
118
+
119
+ }
lib/classes/Paths.php ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once "PathHelper.php";
6
+ use \WebPExpress\PathHelper;
7
+
8
+ include_once "FileHelper.php";
9
+ use \WebPExpress\FileHelper;
10
+
11
+ class Paths
12
+ {
13
+
14
+ public static function createDirIfMissing($dir)
15
+ {
16
+ if (!file_exists($dir)) {
17
+ wp_mkdir_p($dir);
18
+ }
19
+ return file_exists($dir);
20
+ }
21
+
22
+ /**
23
+ * Find out if $dir1 is inside - or equal to - $dir2
24
+ */
25
+ public static function isDirInsideDir($dir1, $dir2)
26
+ {
27
+ $rel = PathHelper::getRelDir($dir2, $dir1);
28
+ return (substr($rel, 0, 3) != '../');
29
+ }
30
+
31
+ // ------------ Home Dir -------------
32
+
33
+ public static function getHomeDirAbs()
34
+ {
35
+ if (!function_exists('get_home_path')) {
36
+ require_once ABSPATH . 'wp-admin/includes/file.php';
37
+ }
38
+ return rtrim(get_home_path(), '/');
39
+ }
40
+
41
+ public static function getHomeDirRel()
42
+ {
43
+ return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getHomeDirAbs());
44
+ }
45
+
46
+ // ------------ Index Dir -------------
47
+ // (The Wordpress installation dir)
48
+
49
+ public static function getIndexDirAbs()
50
+ {
51
+ return rtrim(ABSPATH, '/');
52
+ }
53
+
54
+
55
+ // ------------ .htaccess dir -------------
56
+ // (directory containing the relevant .htaccess)
57
+ // (see https://github.com/rosell-dk/webp-express/issues/36)
58
+
59
+
60
+
61
+ public static function canWriteHTAccessRulesHere($dirName) {
62
+ return FileHelper::canEditOrCreateFileHere($dirName . '/.htaccess');
63
+ }
64
+
65
+ public static function returnFirstWritableHTAccessDir($dirs)
66
+ {
67
+ foreach ($dirs as $dir) {
68
+ if (self::canWriteHTAccessRulesHere($dir)) {
69
+ return $dir;
70
+ }
71
+ }
72
+ return false;
73
+ }
74
+
75
+ // ------------ WP Content Dir -------------
76
+ public static function getWPContentDirAbs()
77
+ {
78
+ return rtrim(WP_CONTENT_DIR, '/');
79
+ }
80
+ public static function getWPContentDirRel()
81
+ {
82
+ return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getWPContentDirAbs());
83
+ }
84
+
85
+ public static function isWPContentDirMoved()
86
+ {
87
+ return (self::getWPContentDirAbs() != (ABSPATH . 'wp-content'));
88
+ }
89
+
90
+ public static function isWPContentDirMovedOutOfAbsPath()
91
+ {
92
+ return !(self::isDirInsideDir(self::getWPContentDirAbs(), ABSPATH));
93
+ }
94
+
95
+
96
+ // ------------ Content Dir -------------
97
+ // (the "webp-express" directory inside wp-content)
98
+
99
+ public static function getContentDirAbs()
100
+ {
101
+ return rtrim(WP_CONTENT_DIR, '/') . '/webp-express';
102
+ }
103
+
104
+ public static function getContentDirRel()
105
+ {
106
+ return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getContentDirAbs());
107
+ }
108
+
109
+ public static function createContentDirIfMissing()
110
+ {
111
+ return self::createDirIfMissing(self::getContentDirAbs());
112
+ }
113
+
114
+ // ------------ Upload Dir -------------
115
+ // (can be moved out of wp-content dir. But not (rarely? - I suppose someone could enter dots) out of abspath)
116
+
117
+ public static function getUploadDirAbs()
118
+ {
119
+ // Pst: When supporting multisite, we could use wp_upload_dir instead
120
+ // https://developer.wordpress.org/reference/functions/wp_upload_dir/
121
+ if ( defined( 'UPLOADS' ) ) {
122
+ return ABSPATH . rtrim(UPLOADS, '/');
123
+ } else {
124
+ return self::getWPContentDirAbs() . '/uploads';
125
+ }
126
+ }
127
+
128
+ public static function isUploadDirMovedOutOfWPContentDir()
129
+ {
130
+ return !(self::isDirInsideDir(self::getUploadDirAbs(), self::getWPContentDirAbs()));
131
+ }
132
+
133
+ public static function isUploadDirMovedOutOfAbsPath()
134
+ {
135
+ return !(self::isDirInsideDir(self::getUploadDirAbs(), ABSPATH));
136
+ }
137
+
138
+ // ------------ Config Dir -------------
139
+
140
+ public static function getConfigDirAbs()
141
+ {
142
+ return self::getContentDirAbs() . '/config';
143
+ }
144
+
145
+ public static function getConfigDirRel()
146
+ {
147
+ return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getConfigDirAbs());
148
+ }
149
+
150
+ public static function createConfigDirIfMissing()
151
+ {
152
+ $configDir = self::getConfigDirAbs();
153
+ // Using code from Wordfence bootstrap.php...
154
+ // Why not simply use wp_mkdir_p ? - it sets the permissions to same as parent. Isn't that better?
155
+ // or perhaps not... - Because we need write permissions in the config dir.
156
+ if (!is_dir($configDir)) {
157
+ @mkdir($configDir, 0775);
158
+ @chmod($configDir, 0775);
159
+ @file_put_contents(rtrim($configDir . '/') . '/.htaccess', <<<APACHE
160
+ <IfModule mod_authz_core.c>
161
+ Require all denied
162
+ </IfModule>
163
+ <IfModule !mod_authz_core.c>
164
+ Order deny,allow
165
+ Deny from all
166
+ </IfModule>
167
+ APACHE
168
+ );
169
+ @chmod($configDir . '/.htaccess', 0664);
170
+ }
171
+ return is_dir($configDir);
172
+ }
173
+
174
+ public static function getConfigFileName()
175
+ {
176
+ return self::getConfigDirAbs() . '/config.json';
177
+ }
178
+
179
+ public static function getWodOptionsFileName()
180
+ {
181
+ return self::getConfigDirAbs() . '/wod-options.json';
182
+ }
183
+
184
+ // ------------ Cache Dir -------------
185
+
186
+ public static function getCacheDirAbs()
187
+ {
188
+ return self::getContentDirAbs() . '/webp-images';
189
+ }
190
+
191
+ public static function getCacheDirRel()
192
+ {
193
+ return PathHelper::getRelDir($_SERVER['DOCUMENT_ROOT'], self::getCacheDirAbs());
194
+ }
195
+
196
+ public static function createCacheDirIfMissing()
197
+ {
198
+ return self::createDirIfMissing(self::getCacheDirAbs());
199
+ }
200
+
201
+ // ------------ Plugin Dir (all plugins) -------------
202
+
203
+ public static function getPluginDirAbs()
204
+ {
205
+ return untrailingslashit(WP_PLUGIN_DIR);
206
+ }
207
+
208
+ public static function isPluginDirMovedOutOfAbsPath()
209
+ {
210
+ return !(self::isDirInsideDir(self::getPluginDirAbs(), ABSPATH));
211
+ }
212
+
213
+ public static function isPluginDirMovedOutOfWpContent()
214
+ {
215
+ return !(self::isDirInsideDir(self::getPluginDirAbs(), self::getWPContentDirAbs()));
216
+ }
217
+
218
+ // ------------ WebP Express Plugin Dir -------------
219
+
220
+ public static function getWebPExpressPluginDirAbs()
221
+ {
222
+ return untrailingslashit(WEBPEXPRESS_PLUGIN_DIR);
223
+ }
224
+
225
+
226
+ // ------------------------------------
227
+ // --------- Url paths ----------
228
+ // ------------------------------------
229
+
230
+ /**
231
+ * Get url path (relative to domain) from absolute url.
232
+ * Ie: "http://example.com/blog" => "blog"
233
+ * Btw: By "url path" we shall always mean relative to domain
234
+ * By "url" we shall always mean complete URL (with domain and everything)
235
+ * (or at least something that starts with it...)
236
+ *
237
+ * Also note that in this library, we never returns trailing or leading slashes.
238
+ */
239
+ public static function getUrlPathFromUrl($url)
240
+ {
241
+ $parsed = parse_url($url);
242
+ if (!isset($parsed['path'])) {
243
+ return '';
244
+ }
245
+ if (is_null($parsed['path'])) {
246
+ return '';
247
+ }
248
+ $path = untrailingslashit($parsed['path']);
249
+ return ltrim($path, '/\\');
250
+ }
251
+
252
+ // Get complete home url (no trailing slash). Ie: "http://example.com/blog"
253
+ public static function getHomeUrl()
254
+ {
255
+ if (!function_exists('get_home_url')) {
256
+ // silence is golden?
257
+ }
258
+ return untrailingslashit(home_url());
259
+ }
260
+
261
+ /** Get home url, relative to domain. Ie "" or "blog"
262
+ * If home url is for example http://example.com/blog/, the result is "blog"
263
+ */
264
+ public static function getHomeUrlPath()
265
+ {
266
+ return self::getUrlPathFromUrl(self::getHomeUrl());
267
+ }
268
+
269
+ /**
270
+ * Get Url to plugin (this is in fact an incomplete URL, you need to append ie '/webp-on-demand.php' to get a full URL)
271
+ */
272
+ public static function getPluginUrl()
273
+ {
274
+ return untrailingslashit(plugins_url('', WEBPEXPRESS_PLUGIN));
275
+ }
276
+
277
+ public static function getPluginUrlPath()
278
+ {
279
+ return self::getUrlPathFromUrl(self::getPluginUrl());
280
+ }
281
+
282
+ public static function getWodUrlPath()
283
+ {
284
+ return self::getPluginUrlPath() . '/wod/webp-on-demand.php';
285
+ }
286
+
287
+ /**
288
+ * Calculate path to existing image, excluding
289
+ * (relative to document root)
290
+ * Ie: "/webp-express-test/wordpress/wp-content/webp-express/webp-images/webp-express-test/wordpress/"
291
+ * This is needed for the .htaccess
292
+ */
293
+ public static function getPathToExisting()
294
+ {
295
+ return self::getCacheDirRel() . '/' . self::getHomeDirRel();
296
+ }
297
+
298
+ public static function getUrlsAndPathsForTheJavascript()
299
+ {
300
+ return [
301
+ 'urls' => [
302
+ 'webpExpressRoot' => self::getPluginUrlPath(),
303
+ ],
304
+ 'filePaths' => [
305
+ 'webpExpressRoot' => self::getWebPExpressPluginDirAbs(),
306
+ 'destinationRoot' => self::getCacheDirAbs()
307
+ ]
308
+ ];
309
+ }
310
+
311
+ }
lib/classes/PlatformInfo.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ class PlatformInfo
6
+ {
7
+
8
+ public static function isMicrosoftIis()
9
+ {
10
+ $server = strtolower($_SERVER['SERVER_SOFTWARE']);
11
+ return ( strpos( $server, 'microsoft-iis') !== false );
12
+ }
13
+
14
+ public static function isApache()
15
+ {
16
+ $server = strtolower($_SERVER['SERVER_SOFTWARE']);
17
+ return ( strpos( $server, 'apache') !== false );
18
+ }
19
+
20
+ public static function isLiteSpeed()
21
+ {
22
+ $server = strtolower($_SERVER['SERVER_SOFTWARE']);
23
+ return ( strpos( $server, 'litespeed') !== false );
24
+ }
25
+
26
+ /**
27
+ * It is not always possible to determine if apache has a given module...
28
+ * We shall not fool anyone into thinking otherwise by providing a "got" method like Wordpress does...
29
+ */
30
+ public static function definitelyNotGotApacheModule($mod)
31
+ {
32
+ if (function_exists( 'apache_get_modules')) {
33
+ $mods = apache_get_modules();
34
+ if (!in_array($mod, $mods)) {
35
+ return true;
36
+ }
37
+ }
38
+ // TODO: Perhaps also try looking at phpinfo, like Wordpress does in apache_mod_loaded
39
+
40
+ return false;
41
+ }
42
+
43
+ public static function definitelyGotApacheModule($mod)
44
+ {
45
+ if (function_exists( 'apache_get_modules')) {
46
+ $mods = apache_get_modules();
47
+ if (in_array($mod, $mods)) {
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ }
53
+
54
+ public static function definitelyNotGotModRewrite()
55
+ {
56
+ return self::definitelyNotGotApacheModule('mod_rewrite');
57
+ }
58
+
59
+ public static function definitelyGotModEnv()
60
+ {
61
+ return self::definitelyGotApacheModule('mod_env');
62
+ }
63
+
64
+
65
+ }
lib/classes/State.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ /**
6
+ * Store state in db
7
+ * We are using update_option WITHOUT autoloading.
8
+ * So this class is not intended for storing stuff that is needed on every page load.
9
+ * For such things, use update_option / get_option directly
10
+ */
11
+
12
+ class State
13
+ {
14
+
15
+ public static function getStateObj() {
16
+ // TODO: cache
17
+ $json = get_option('webp-express-state', '[]');
18
+ return json_decode($json, true);
19
+ }
20
+
21
+ /**
22
+ * Return state by key. Returns supplied default if key doesn't exist, or state object is corrupt
23
+ */
24
+ public static function getState($key, $default = null) {
25
+ $obj = self::getStateObj();
26
+ if ($obj != false) {
27
+ if (isset($obj[$key])) {
28
+ return $obj[$key];
29
+ }
30
+ }
31
+ return $default;
32
+ }
33
+
34
+ public static function setState($key, $value) {
35
+ $currentStateObj = self::getStateObj();
36
+ $currentStateObj[$key] = $value;
37
+ $json = json_encode($currentStateObj, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
38
+
39
+ // Store in db. No autoloading.
40
+ update_option('webp-express-state', $json, false);
41
+ }
42
+ }
lib/deactivate.php CHANGED
@@ -1,11 +1,37 @@
1
  <?php
2
- include_once( plugin_dir_path( __FILE__ ) . 'helpers.php');
3
 
4
- class WebPExpressDeactivate {
 
5
 
6
- public function deactivate() {
7
- WebPExpressHelpers::insertHTAccessRules("# deactivated");
8
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
- WebPExpressDeactivate::deactivate();
 
 
 
 
 
 
 
 
1
  <?php
 
2
 
3
+ include_once __DIR__ . '/classes/Actions.php';
4
+ use \WebPExpress\Actions;
5
 
6
+ /*include_once __DIR__ . '/classes/Config.php';
7
+ use \WebPExpress\Config;*/
8
+
9
+ include_once __DIR__ . '/classes/HTAccess.php';
10
+ use \WebPExpress\HTAccess;
11
+
12
+ include_once __DIR__ . '/classes/Messenger.php';
13
+ use \WebPExpress\Messenger;
14
+
15
+ include_once __DIR__ . '/classes/Paths.php';
16
+ use \WebPExpress\Paths;
17
+
18
+
19
+
20
+ function webpexpress_deny_deactivate($msg) {
21
+ Messenger::addMessage(
22
+ 'error',
23
+ $msg
24
+ );
25
+ wp_redirect( $_SERVER['HTTP_REFERER']);
26
+ exit;
27
  }
28
 
29
+ $result = HTAccess::deactivateHTAccessRules();
30
+ if ($result !== true) {
31
+ // Oh no. We failed removing the rules
32
+ $msg = "<b>Sorry, can't let you disable WebP Express!</b><br>" .
33
+ 'There are rewrite rules in the <i>.htaccess</i> that could not be removed. If these are not removed, it would break all images.<br>' .
34
+ 'Please make your <i>.htaccess</i> writable and then try to disable WebPExpress again.<br>Alternatively, remove the rules manually in your <i>.htaccess</i> file and try disabling again.' .
35
+ '<br>It concerns the following files:<br>' . implode('<br>', $result);
36
+ webpexpress_deny_deactivate($msg);
37
+ }
lib/debug.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ function webpexpress_activated() {
5
+ update_option( 'webp-express-activation-error', ob_get_contents() );
6
+ }
7
+ add_action( 'activated_plugin', 'webpexpress_activated' );
8
+ if (!empty(get_option('webp-express-activation-error'))) {
9
+ add_filter( 'admin_footer_text', function() {
10
+ return 'Activation error:' . get_option('webp-express-activation-error');
11
+ });
12
+ }
lib/helpers.php DELETED
@@ -1,311 +0,0 @@
1
- <?php
2
-
3
- class WebPExpressHelpers
4
- {
5
-
6
- public static function calculateUrlsAndPaths()
7
- {
8
- // Calculate URL's
9
- $upload_dir = wp_upload_dir();
10
- $destinationRootUrlPathAbs = $upload_dir['baseurl'] . '/' . 'webp-express';
11
-
12
- // Calculate url path to directory of image converter
13
- $pluginUrlAbs = plugins_url('', WEBPEXPRESS_PLUGIN);
14
-
15
- $converterUrlPath = parse_url($pluginUrlAbs)['path'];
16
-
17
- // Calculate destination root url path
18
- $destinationRootUrlPath = parse_url($destinationRootUrlPathAbs)['path'];
19
-
20
- // Calculate Wordpress url path
21
- // If site for example is accessed example.com/blog/, then the url path is "blog"
22
- $wpUrlPath = trailingslashit(parse_url(site_url())['path']); // ie "/blog/" or "/"
23
-
24
- $converterUrlPathRelativeToSiteUrl = WebPExpressHelpers::get_rel_dir(untrailingslashit($wpUrlPath), $converterUrlPath);
25
- $siteUrlPathRelativeToConverterPath = WebPExpressHelpers::get_rel_dir($converterUrlPath, untrailingslashit($wpUrlPath));
26
-
27
-
28
- // Calculate file dirs
29
- // --------------------
30
-
31
- $destinationRoot = trailingslashit($upload_dir['basedir']) . 'webp-express';
32
-
33
- $WebExpressRoot = untrailingslashit(WEBPEXPRESS_PLUGIN_DIR);
34
-
35
- $destinationRootRelativeToWebExpressRoot = untrailingslashit(WebPExpressHelpers::get_rel_dir($WebExpressRoot, $destinationRoot));
36
-
37
- // $destinationRoot is ie '/webp-express-test/wordpress/wp-content/uploads/webp-express/'
38
- // $wordpressRoot is ie '/mnt/Work/playground/webp-express-test/wordpress/'
39
- $wordpressRoot = untrailingslashit(ABSPATH);
40
- $destinationRootRelativeToWordpressRoot = untrailingslashit(WebPExpressHelpers::get_rel_dir($wordpressRoot . '/', $destinationRoot));
41
-
42
- return [
43
- 'urls' => [
44
- 'webpExpressRoot' => untrailingslashit($converterUrlPath),
45
- 'convert' => untrailingslashit($converterUrlPath) . 'convert.php',
46
- 'destinationRoot' => untrailingslashit($destinationRootUrlPath),
47
- 'wpRoot' => untrailingslashit($wpUrlPath), // ie "/blog" or ""
48
- 'converterUrlPathRelativeToSiteUrl' => $converterUrlPathRelativeToSiteUrl,
49
- 'siteUrlPathRelativeToConverterPath' => $siteUrlPathRelativeToConverterPath
50
- ],
51
- 'filePaths' => [
52
- 'wordpressRoot' => $wordpressRoot,
53
- 'destinationRoot' => $destinationRoot,
54
- 'webpExpressRoot' => $WebExpressRoot,
55
- 'destinationRootRelativeToWebExpressRoot' => $destinationRootRelativeToWebExpressRoot,
56
- 'destinationRootRelativeToWordpressRoot' => $destinationRootRelativeToWordpressRoot
57
- ],
58
- // TODO: read up on this, and make complete tests
59
- // https://wordpress.stackexchange.com/questions/188448/whats-the-difference-between-get-home-path-and-abspath
60
- 'pathsForHtaccess' => [
61
- //$basePath, $destinationRoot, $scriptPath
62
- 'basePath' => untrailingslashit(WebPExpressHelpers::get_rel_dir($_SERVER['DOCUMENT_ROOT'], untrailingslashit(ABSPATH))),
63
- 'destinationRoot' => $destinationRootRelativeToWordpressRoot, // Where to place converted files, relative to the base path.
64
- 'scriptPath' => untrailingslashit($converterUrlPathRelativeToSiteUrl),
65
-
66
- //'abspath' => ABSPATH,
67
- //'dr' => $_SERVER['DOCUMENT_ROOT'],
68
- //'bp' => str_replace($_SERVER['DOCUMENT_ROOT'] . '/', '', untrailingslashit(ABSPATH)),
69
- ]
70
- ];
71
- }
72
-
73
- public static function generateHTAccessRules()
74
- {
75
- //global $wpdb;
76
- //$hasWebPExpressOptionBeenSaved = ($wpdb->get_row( "SELECT * FROM $wpdb->options WHERE option_name = 'webp_express_converters'" ) !== null);
77
- //if (!$hasWebPExpressOptionBeenSaved) {
78
-
79
- if (empty(get_option('webp-express-configured')) || empty(get_option('webp_express_converters'))) {
80
- // This should not happen, because generateHTAccessRules should not be called at this stage.
81
- // But if it did happen anyway, better to exit with a comment than failing totally.
82
- return '# Cannot generate the htaccess rules yet. - WebP Express has not been configured yet.';
83
- }
84
- $options = '';
85
- $options .= '&max-quality=' . get_option('webp_express_max_quality', '85');
86
- //$options .= '&method=' . get_option('webp_express_method');
87
- $options .= '&fail=' . get_option('webp_express_failure_response', 'original');
88
- $options .= '&critical-fail=report';
89
-
90
- $converters_and_options = json_decode(get_option('webp_express_converters'), true);
91
-
92
- $converter_options = '';
93
- foreach ($converters_and_options as $converter) {
94
- if (isset($converter['deactivated'])) continue;
95
- $converters[] = $converter['converter'];
96
- if (isset($converter['options'])) {
97
- foreach ($converter['options'] as $converter_option => $converter_value) {
98
- $converter_options .= '&' . $converter['id'] . '-' . $converter_option . '=' . $converter_value;
99
- }
100
- };
101
- }
102
- $options .= '&converters=' . implode(',', $converters);
103
- $options .= $converter_options;
104
-
105
- $urlsAndPaths = WebPExpressHelpers::calculateUrlsAndPaths();
106
- $urls = $urlsAndPaths['urls'];
107
- $filePaths = $urlsAndPaths['filePaths'];
108
-
109
- $imageTypes = get_option('webp_express_image_types_to_convert', 1);
110
- $fileExtensions = [];
111
- if ($imageTypes & 1) {
112
- $fileExtensions[] = 'jpe?g';
113
- }
114
- if ($imageTypes & 2) {
115
- $fileExtensions[] = 'png';
116
- }
117
- $fileExt = implode('|', $fileExtensions);
118
-
119
- $paths = $urlsAndPaths['pathsForHtaccess'];
120
- return self::generateHTAccessRules2($fileExt, $paths['basePath'], $paths['destinationRoot'], $paths['scriptPath'], $options);
121
- }
122
- /*
123
- if ($imageTypes == 0) {
124
- $rules = '# Configured not to convert anything!';
125
- //$rules .= 'php_value include_path ".:/usr/local/lib/php:/your/dir"';
126
- $rules .= 'php_value include_path ".:/usr/local/lib/php:/hsphere/local/home/z84733/mingo.net/wp-content/plugins/webp-express/vendor/webp-convert/Converters/Binaries"';
127
- } else {
128
- $rules = "<IfModule mod_rewrite.c>\n" .
129
-
130
- " RewriteEngine On\n\n" .
131
- " # Redirect to existing converted image (under appropriate circumstances)\n" .
132
- " RewriteCond %{HTTP_ACCEPT} image/webp\n" .
133
- " RewriteCond %{QUERY_STRING} !((^reconvert.*)|(^debug.*))\n" .
134
- " RewriteCond %{DOCUMENT_ROOT}" . $urls['destinationRoot'] . "/$1.$2.webp -f\n" .
135
- " RewriteRule ^\\/?(.*)\.(" . $fileExt . ")$ " . $urls['destinationRoot'] . "/$1.$2.webp [NC,T=image/webp,E=webpaccept:1,E=WEBPEXISTING:1,QSD]\n\n" .
136
- " # Redirect to image converter (under appropriate circumstances)\n" .
137
- " RewriteCond %{HTTP_ACCEPT} image/webp\n" .
138
- " RewriteCond %{QUERY_STRING} (^reconvert.*)|(^debug.*) [OR]\n" .
139
- " RewriteCond %{DOCUMENT_ROOT}" . $urls['destinationRoot'] . "/$1.$2.webp !-f\n" .
140
- " RewriteCond %{QUERY_STRING} (.*)\n" .
141
- //" RewriteRule ^\\/?(.*)\.(" . $fileExt . ")$ " . $urls['converterUrlPathRelativeToSiteUrl'] . "convert.php?source=" . $urls['siteUrlPathRelativeToConverterPath'] . "$1.$2&destination-root=" . $filePaths['destinationRootRelativeToWebExpressRoot'] . $options . "&%1 [NC,E=accept:1]\n" .
142
- " RewriteRule ^\\/?(.*)\.(" . $fileExt . ")$ " . $urls['converterUrlPathRelativeToSiteUrl'] . "convert.php?htaccess-path=" . $filePaths['wordpressRoot'] . '&destination-root-rel-to-htaccess-path=' . $filePaths['destinationRootRelativeToWordpressRoot'] . '&source-rel-to-htaccess-path=$1.$2' . $options . "&%1 [NC,E=webpaccept:1,E=WEBPNEW]\n" .
143
-
144
- "</IfModule>\n" .
145
-
146
- "<IfModule mod_headers.c>\n" .
147
- " # Apache appends \"REDIRECT_\" in front of the environment variables, but LiteSpeed does not\n" .
148
- " # These next three lines are for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
149
- " SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1\n" .
150
- " SetEnvIf REDIRECT_WEBPEXISTING 1 WEBPEXISTING=1\n" .
151
- " SetEnvIf REDIRECT_WEBPNEW 1 WEBPNEW=1\n\n" .
152
-
153
- " # Make CDN caching possible." .
154
- " Header append Vary Accept env=WEBPACCEPT\n\n" .
155
-
156
- " # Add headers for debugging\n" .
157
- " Header append X-WebP-Express \"Routed to existing converted image\" env=WEBPEXISTING\n" .
158
- " Header append X-WebP-Express \"Routed to image converter\" env=WEBPNEW\n" .
159
- "</IfModule>\n\n" .
160
- "AddType image/webp .webp\n";
161
- }
162
- return $rules;
163
- }
164
- */
165
- /**
166
- Create rewrite rules for WebP On Demand.
167
-
168
- @param $fileExt To convert both jpegs and pngs, use "jpe?g|png". To disable converting, use ""
169
- @param $basePath Path of the .htaccess relative to document root. Ie "." or "my-sub-site"
170
- @param $destinationRoot Where to place converted files, relative to the base path.
171
- @param $scriptPath Url path to webp-on-demand.php, relative to the base directory.
172
- @param $options String of options. If not empty, it must start with "&". Ie "&converters=cwebp,gd&quality=auto"
173
-
174
- Note: None of the paths supplied may start or end with a forward slash.
175
- */
176
- private static function generateHTAccessRules2($fileExt, $basePath, $destinationRoot, $scriptPath, $options)
177
- {
178
- if ($basePath == '') {
179
- //$basePath = '.';
180
- }
181
- $rules = '';
182
- if ($fileExt == '') {
183
- $rules .= '# Configured not to convert anything!';
184
- } else {
185
- $rules .= "<IfModule mod_rewrite.c>\n" .
186
-
187
- " RewriteEngine On\n\n" .
188
-
189
- " # Redirect to existing converted image (under appropriate circumstances)\n" .
190
- " RewriteCond %{HTTP_ACCEPT} image/webp\n" .
191
- " RewriteCond %{QUERY_STRING} !((^reconvert.*)|(^debug.*))\n" .
192
- " RewriteCond %{DOCUMENT_ROOT}/" . $basePath . "/" . $destinationRoot . "/$1.$2.webp -f\n" .
193
- " RewriteRule ^\/?(.*)\.(jpe?g|png)$ /" . $basePath . "/" . $destinationRoot . "/$1.$2.webp [NC,T=image/webp,E=WEBPACCEPT:1,E=WEBPEXISTING:1,QSD]\n\n" .
194
-
195
- " # Redirect to converter (under appropriate circumstances)\n" .
196
- " RewriteCond %{HTTP_ACCEPT} image/webp\n" .
197
- " RewriteCond %{QUERY_STRING} (^reconvert.*)|(^debug.*) [OR]\n" .
198
- " RewriteCond %{DOCUMENT_ROOT}/" . $basePath . "/" . $destinationRoot . "/$1.$2.webp !-f\n" .
199
- " RewriteCond %{QUERY_STRING} (.*)\n" .
200
- " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ " . $scriptPath . "/webp-on-demand.php?base-path=" . $basePath . "&destination-root=" . $destinationRoot . "&source=$1.$2" . $options . "&%1 [NC,E=WEBPACCEPT:1,E=WEBPNEW:1]\n" .
201
- "</IfModule>\n\n" .
202
-
203
- "<IfModule mod_headers.c>\n" .
204
- " # Apache appends \"REDIRECT_\" in front of the environment variables, but LiteSpeed does not\n" .
205
- " # These next three lines are for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
206
- " SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1\n" .
207
- " SetEnvIf REDIRECT_WEBPEXISTING 1 WEBPEXISTING=1\n" .
208
- " SetEnvIf REDIRECT_WEBPNEW 1 WEBPNEW=1\n\n" .
209
-
210
- " # Make CDN caching possible.\n" .
211
- " Header append Vary Accept env=WEBPACCEPT\n\n" .
212
-
213
- " # Add headers for debugging\n" .
214
- " Header append X-WebP-On-Demand \"Routed to existing converted image\" env=WEBPEXISTING\n" .
215
- " Header append X-WebP-On-Demand \"Routed to image converter\" env=WEBPNEW\n" .
216
- "</IfModule>\n\n" .
217
- "AddType image/webp .webp\n";
218
- }
219
- return $rules;
220
- }
221
-
222
- // Insert .htaccess rules.
223
- // @return (bool) True if successful, false if not.
224
- public static function doInsertHTAccessRules($rules) {
225
- if (!function_exists('get_home_path')) {
226
- require_once ABSPATH . 'wp-admin/includes/file.php';
227
- }
228
- $root_path = get_home_path();
229
-
230
- if (!file_exists($root_path . '.htaccess')) {
231
- return false;
232
- }
233
-
234
- $file_existing_permission = '';
235
-
236
- // Try to make .htaccess writable if its not
237
- if (file_exists($root_path . '.htaccess') && !is_writable($root_path . '.htaccess')) {
238
- // Store existing permissions, so we can revert later
239
- $file_existing_permission = octdec(substr(decoct(fileperms($root_path . '.htaccess')), -4));
240
-
241
- // Try to chmod.
242
- // It may fail, but we can ignore that. If it fails, insert_with_markers will also fail
243
- chmod($root_path . '.htaccess', 0550);
244
- }
245
-
246
-
247
- /* Add rules to .htaccess */
248
- if (!function_exists('insert_with_markers')) {
249
- require_once ABSPATH . 'wp-admin/includes/misc.php';
250
- }
251
- if (!insert_with_markers($root_path . '.htaccess', 'WebP Express', $rules)) {
252
- return false;
253
- }
254
- else {
255
- /* Revert File Permission */
256
- if (!empty($file_existing_permission)) {
257
- chmod($root_path . '.htaccess', $file_existing_permission);
258
- }
259
- return true;
260
- }
261
-
262
- }
263
-
264
- public static function insertHTAccessRules($rules)
265
- {
266
- if (self::doInsertHTAccessRules($rules)) {
267
- update_option('webp-express-message-pending', true, false );
268
- update_option('webp-express-inserted-rules-ok', true, false);
269
- } else {
270
- update_option('webp-express-failed-inserting-rules', true, false);
271
- update_option('webp-express-deactivate', true, false);
272
- }
273
- }
274
-
275
- /* Get relative path between one dir and the other.
276
- ie
277
- from: /var/www/wordpress/wp-content/plugins/webp-express
278
- to: /var/www/wordpress/wp-content/uploads
279
- result: ../../uploads/
280
-
281
- or
282
- from: /mnt/Work/playground/webp-express-test/wordpress/
283
- to: /mnt/Work/playground/webp-express-test/wordpress/wp-content/uploads/webp-express
284
- result: wp-content/uploads/webp-express
285
-
286
- */
287
- public static function get_rel_dir($from_dir, $to_dir) {
288
- $from_dir = untrailingslashit($from_dir);
289
- $to_dir = untrailingslashit($to_dir);
290
-
291
- $from_dir_parts = explode('/', str_replace( '\\', '/', $from_dir ));
292
- $to_dir_parts = explode('/', str_replace( '\\', '/', $to_dir ));
293
- $i = 0;
294
- while (($i < count($from_dir_parts)) && ($i < count($to_dir_parts)) && ($from_dir_parts[$i] == $to_dir_parts[$i])) {
295
- $i++;
296
- }
297
- $rel = "";
298
- for ($j = $i; $j < count($from_dir_parts); $j++) {
299
- $rel .= "../";
300
- }
301
-
302
- for ($j = $i; $j < count($to_dir_parts); $j++) {
303
- $rel .= $to_dir_parts[$j] . '/';
304
- }
305
- if ($rel == '') {
306
- $rel = '.';
307
- }
308
- return $rel;
309
- }
310
-
311
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/message.php DELETED
@@ -1,131 +0,0 @@
1
- <?php
2
-
3
- delete_option( 'webp-express-message-pending');
4
-
5
- if ( get_option( 'webp-express-deactivate' ) ) {
6
- add_action( 'admin_notices', function() {
7
- printf(
8
- '<div class="%1$s"><p>%2$s</p></div>',
9
- esc_attr( 'notice notice-error is-dismissible' ),
10
- __( 'Plugin <b>deactivated</b>' )
11
- );
12
- });
13
- }
14
-
15
- add_action( 'admin_notices', function() {
16
- // Possible classes:
17
- // notice-warning, notice-error, notice-warning, notice-success, or notice-info
18
- // - add is-dismissible
19
-
20
- if ( get_option( 'webp-express-inserted-rules-ok' ) ) {
21
- delete_option( 'webp-express-inserted-rules-ok');
22
- printf(
23
- '<div class="%1$s"><p>%2$s</p></div>',
24
- esc_attr( 'notice notice-success is-dismissible' ),
25
- esc_html( __( 'WebP Express updated .htaccess', 'webp-express' ) )
26
- );
27
- }
28
-
29
-
30
- if ( get_option( 'webp-express-just-activated' ) ) {
31
- delete_option( 'webp-express-just-activated');
32
-
33
- if ( get_option( 'webp-express-microsoft-iis' ) ) {
34
- delete_option( 'webp-express-microsoft-iis');
35
- printf(
36
- '<div class="%1$s"><p>%2$s</p></div>',
37
- esc_attr( 'notice notice-error is-dismissible' ),
38
- esc_html( __( 'You are on Microsof IIS server. The plugin does not work on IIS', 'webp-express' ) )
39
- );
40
- return;
41
- } else if ( get_option( 'webp-express-not-apache-nor-litespeed' ) ) {
42
- delete_option( 'webp-express-not-apache-nor-litespeed');
43
- printf(
44
- '<div class="%1$s"><p>%2$s</p></div>',
45
- esc_attr( 'notice notice-warning is-dismissible' ),
46
- esc_html( __( 'You are not on Apache server, nor on LiteSpeed. WebP Express has only been tested on Apache and LiteSpeed - continue at own risk (but please tell me if it works!). Your server is: ' . $_SERVER['SERVER_SOFTWARE'], 'webp-express' ) )
47
- );
48
- }
49
-
50
- if ( get_option( 'webp-express-no-multisite' ) ) {
51
- delete_option( 'webp-express-no-multisite');
52
- printf(
53
- '<div class="%1$s"><p>%2$s</p></div>',
54
- esc_attr( 'notice notice-error is-dismissible' ),
55
- esc_html( __( 'Sorry, WebP Express does not support multisite websites (yet). A donation might resolve the issue ;-)', 'webp-express' ) )
56
- );
57
- return;
58
- }
59
-
60
- if (get_option( 'webp-express-php-too-old' ) ) {
61
- delete_option( 'webp-express-php-too-old');
62
- printf(
63
- '<div class="%1$s"><p>%2$s</p></div>',
64
- esc_attr( 'notice notice-warning is-dismissible' ),
65
- esc_html( sprintf(__( 'You are on a very old version of PHP (%s). WebP Express may not work as intended.', 'webp-express' ), phpversion() ) )
66
- );
67
- return;
68
- }
69
-
70
- if ( get_option( 'webp-express-failed-creating-upload-dir' ) ) {
71
- delete_option( 'webp-express-failed-creating-upload-dir');
72
- printf(
73
- '<div class="%1$s"><p>%2$s</p></div>',
74
- esc_attr( 'notice notice-error is-dismissible' ),
75
- //esc_html( __( 'WebP Express could not create a subfolder in your upload folder. Check your file permissions', 'webp-express' ) )
76
- '<b>WebP Express could not create a subfolder in your upload folder</b>. Check your file permissions. <a target="_blank" href="https://github.com/rosell-dk/webp-express/wiki/Error-messages-and-warnings#webp-express-could-not-create-a-subfolder-in-your-upload-folder">Click here</a> for more information.'
77
- );
78
- return;
79
- }
80
-
81
- /*
82
- if ( get_option( 'webp-express-htaccess-not-writable' ) ) {
83
- delete_option( 'webp-express-htaccess-not-writable');
84
- printf(
85
- '<div class="%1$s"><p>%2$s</p></div>',
86
- esc_attr( 'notice notice-error is-dismissible' ),
87
- '<b>.htaccess is not writable</b>. The plugin has been disabled. To fix this, make .htaccess writable and try activating the plugin again. <a target="_blank" href="https://github.com/rosell-dk/webp-express/wiki/Error-messages-and-warnings#htaccess-is-not-writable">Click here</a> for more information.'
88
- //esc_html( __( '.htaccess is not writable. As WebP Express needs to write its redirection rules to .htaccess in order to function, the plugin has been disabled. To fix this, make .htaccess writable and try activating the plugin again. The file is located in the root of your wordpress installation.', 'webp-express' ) )
89
- );
90
- return;
91
- }
92
-
93
- if ( get_option( 'webp-express-failed-inserting-rules' ) ) {
94
- delete_option( 'webp-express-failed-inserting-rules');
95
- printf(
96
- '<div class="%1$s"><p>%2$s</p></div>',
97
- esc_attr( 'notice notice-error is-dismissible' ),
98
- esc_html( __( 'WebP Express failed writing rules to .htaccess. The file is writable, but something went wrong writing to it anyway. More precicely, the Wordpress function "insert_with_markers" returned false. The .htaccess rules are needed for this plugin to function, and the plugin has therefore been deactivated.', 'webp-express' ) )
99
- );
100
- return;
101
- }*/
102
-
103
- if ( get_option( 'webp-express-failed-inserting-rules' ) ) {
104
- delete_option( 'webp-express-failed-inserting-rules');
105
- printf(
106
- '<div class="%1$s"><p>%2$s</p></div>',
107
- esc_attr( 'notice notice-error is-dismissible' ),
108
- '<b>.htaccess is not writable</b>. To fix this, make .htaccess writable and try activating the plugin again. <a target="_blank" href="https://github.com/rosell-dk/webp-express/wiki/Error-messages-and-warnings#htaccess-is-not-writable">Click here</a> for more information.'
109
- );
110
- return;
111
- }
112
-
113
- if (empty(get_option('webp-express-configured'))) {
114
- printf(
115
- '<div class="%1$s"><p>%2$s</p></div>',
116
- esc_attr( 'notice notice-info is-dismissible' ),
117
- 'WebP Express was installed successfully. To start using it, you must <a href="options-general.php?page=webp_express_settings_page">configure it here</a>.'
118
- );
119
- } else {
120
- printf(
121
- '<div class="%1$s"><p>%2$s</p></div>',
122
- esc_attr( 'notice notice-info is-dismissible' ),
123
- 'WebP Express reactivated successfully.<br>The image redirections should be in effect again (you should see a "WebP Express updated .htaccess" message above this...)<br><br>Just a quick reminder: If you at some point change the upload directory or move Wordpress, you will have to regenerate the .htaccess.<br>You do that by changing the configuration <a href="options-general.php?page=webp_express_settings_page">(here)</a>'
124
- );
125
- }
126
- }
127
- });
128
-
129
-
130
-
131
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/migrate/migrate.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/../classes/State.php';
4
+ use \WebPExpress\State;
5
+
6
+
7
+ /*
8
+ In 0.4.0, we had a 'webp-express-configured' option.
9
+ As long as there are still users on 0.4 or below, we must do the following:
10
+ */
11
+ if (get_option('webp-express-configured', false)) {
12
+ State::setState('configured', true);
13
+ }
14
+
15
+ /*
16
+ In 0.1, we did not have the 'webp-express-configured' option.
17
+ To determine if WebP Express was configured in 0.1, we can test the (now obsolete) webp_express_converters option
18
+ As long as there are still users on 0.1, we must do the following:
19
+ */
20
+ if (!get_option('webp-express-configured', false)) {
21
+ if (!is_null(get_option('webp_express_converters', null))) {
22
+ State::setState('configured', true);
23
+ }
24
+ }
25
+
26
+
27
+ if (!(State::getState('configured', false))) {
28
+ // Options has never has been saved, so no migration is needed.
29
+ // We can set migrate-version to current
30
+ update_option('webp-express-migration-version', WEBPEXPRESS_MIGRATION_VERSION);
31
+ } else {
32
+
33
+ if (intval(get_option('webp-express-migration-version', 0)) == 0) {
34
+ // run migration 1
35
+ // It must take care of updating migration-version to 1, - if successful.
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
+ }
lib/migrate/migrate1.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WebPExpress;
4
+
5
+ include_once __DIR__ . '/../classes/Config.php';
6
+ use \WebPExpress\Config;
7
+
8
+ include_once __DIR__ . '/../classes/HTAccess.php';
9
+ use \WebPExpress\HTAccess;
10
+
11
+ include_once __DIR__ . '/../classes/Paths.php';
12
+ use \WebPExpress\Paths;
13
+
14
+ include_once __DIR__ . '/../classes/Messenger.php';
15
+ use \WebPExpress\Messenger;
16
+
17
+ //Messenger::addMessage('info', 'migration:' . get_option('webp-express-migration-version', 'not set'));
18
+
19
+ // On successful migration:
20
+ // update_option('webp-express-migration-version', '1', true);
21
+
22
+ function webp_express_migrate1_createFolders()
23
+ {
24
+ if (!Paths::createContentDirIfMissing()) {
25
+ Messenger::printMessage(
26
+ 'error',
27
+ 'For migration to 0.5.0, WebP Express needs to create a directory "webp-express" under your wp-content folder, but does not have permission to do so.<br>' .
28
+ 'Please create the folder manually, or change the file permissions of your wp-content folder.'
29
+ );
30
+ return false;
31
+ } else {
32
+ if (!Paths::createConfigDirIfMissing()) {
33
+ Messenger::printMessage(
34
+ 'error',
35
+ 'For migration to 0.5.0, WebP Express needs to create a directory "webp-express/config" under your wp-content folder, but does not have permission to do so.<br>' .
36
+ 'Please create the folder manually, or change the file permissions.'
37
+ );
38
+ return false;
39
+ }
40
+
41
+
42
+ if (!Paths::createCacheDirIfMissing()) {
43
+ Messenger::printMessage(
44
+ 'error',
45
+ 'For migration to 0.5.0, WebP Express needs to create a directory "webp-express/webp-images" under your wp-content folder, but does not have permission to do so.<br>' .
46
+ 'Please create the folder manually, or change the file permissions.'
47
+ );
48
+ return false;
49
+ }
50
+ }
51
+ return true;
52
+ }
53
+
54
+ function webp_express_migrate1_createDummyConfigFiles()
55
+ {
56
+ // TODO...
57
+ return true;
58
+ }
59
+
60
+ function webpexpress_migrate1_migrateOptions()
61
+ {
62
+ $converters = json_decode(get_option('webp_express_converters', '[]'), true);
63
+ foreach ($converters as &$converter) {
64
+ unset ($converter['id']);
65
+ }
66
+
67
+ $options = [
68
+ 'image-types' => intval(get_option('webp_express_image_types_to_convert', 1)),
69
+ 'max-quality' => intval(get_option('webp_express_max_quality', 80)),
70
+ 'fail' => get_option('webp_express_failure_response', 'original'),
71
+ 'converters' => $converters,
72
+ 'forward-query-string' => true
73
+ ];
74
+ if ($options['max-quality'] == 0) {
75
+ $options['max-quality'] = 80;
76
+ if ($options['image-types'] == 0) {
77
+ $options['image-types'] = 1;
78
+ }
79
+ }
80
+ if ($options['converters'] == null) {
81
+ $options['converters'] = [];
82
+ }
83
+
84
+ // TODO: Save
85
+ //Messenger::addMessage('info', 'Options: <pre>' . print_r($options, true) . '</pre>');
86
+ // $htaccessExists = Config::doesHTAccessExists();
87
+
88
+ $config = $options;
89
+
90
+ //$htaccessExists = Config::doesHTAccessExists();
91
+ $rules = HTAccess::generateHTAccessRulesFromConfigObj($config);
92
+
93
+ if (Config::saveConfigurationFile($config)) {
94
+ $options = Config::generateWodOptionsFromConfigObj($config);
95
+ if (Config::saveWodOptionsFile($options)) {
96
+
97
+ //Config::saveConfigurationAndHTAccessFilesWithMessages($config, 'migrate');
98
+ $rulesResult = HTAccess::saveRules($config);
99
+ /*
100
+ 'mainResult' // 'index', 'wp-content' or 'failed'
101
+ 'minRequired' // 'index' or 'wp-content'
102
+ 'pluginToo' // 'yes', 'no' or 'depends'
103
+ 'pluginFailed' // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
104
+ 'pluginFailedBadly' // true if plugin failed AND it seems we have rewrite rules there
105
+ 'overidingRulesInWpContentWarning' // true if main result is 'index' but we cannot remove those in wp-content
106
+ 'rules' // the rules that were generated
107
+ */
108
+ $mainResult = $rulesResult['mainResult'];
109
+ $rules = $rulesResult['rules'];
110
+
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(
118
+ 'warning',
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
+ }
126
+ } else {
127
+ Messenger::addMessage(
128
+ 'error',
129
+ 'For migration to 0.5.0, WebP Express failed saving options file. ' .
130
+ 'You must grant us write access to your wp-config folder.<br>' .
131
+ 'Tried to save to: "' . Paths::getWodOptionsFileName() . '"' .
132
+ 'Fix the file permissions and reload<br>'
133
+ );
134
+ return false;
135
+ }
136
+ } else {
137
+ Messenger::addMessage(
138
+ 'error',
139
+ 'For migration to 0.5.0, WebP Express failed saving configuration file.<br>' .
140
+ 'You must grant us write access to your wp-config folder.<br>' .
141
+ 'Tried to save to: "' . Paths::getConfigFileName() . '"' .
142
+ 'Fix the file permissions and reload<br>'
143
+ );
144
+ return false;
145
+ }
146
+
147
+ //saveConfigurationFile
148
+ //return $options;
149
+ return true;
150
+ }
151
+
152
+ function webpexpress_migrate1_deleteOldOptions() {
153
+ $optionsToDelete = [
154
+ 'webp_express_max_quality',
155
+ 'webp_express_image_types_to_convert',
156
+ 'webp_express_failure_response',
157
+ 'webp_express_converters',
158
+ 'webp-express-inserted-rules-ok',
159
+ 'webp-express-configured',
160
+ 'webp-express-pending-messages',
161
+ 'webp-express-just-activated',
162
+ 'webp-express-message-pending',
163
+ 'webp-express-failed-inserting-rules',
164
+ 'webp-express-deactivate',
165
+ 'webp_express_fail_action',
166
+ 'webp_express_method',
167
+ 'webp_express_quality'
168
+
169
+ ];
170
+ foreach ($optionsToDelete as $i => $optionName) {
171
+ delete_option($optionName);
172
+ }
173
+ }
174
+
175
+ /* helper. Remove dir recursively. No warnings - fails silently */
176
+ function webpexpress_migrate1_rrmdir($dir) {
177
+ if (@is_dir($dir)) {
178
+ $objects = @scandir($dir);
179
+ foreach ($objects as $object) {
180
+ if ($object != "." && $object != "..") {
181
+ if (@is_dir($dir."/".$object))
182
+ webpexpress_migrate1_rrmdir($dir."/".$object);
183
+ else
184
+ @unlink($dir."/".$object);
185
+ }
186
+ }
187
+ @rmdir($dir);
188
+ }
189
+ }
190
+
191
+ function webpexpress_migrate1_deleteOldWebPImages() {
192
+ $upload_dir = wp_upload_dir();
193
+ $destinationRoot = trailingslashit($upload_dir['basedir']) . 'webp-express';
194
+ webpexpress_migrate1_rrmdir($destinationRoot);
195
+ }
196
+
197
+ if (webp_express_migrate1_createFolders()) {
198
+ if (webp_express_migrate1_createDummyConfigFiles()) {
199
+ if (webpexpress_migrate1_migrateOptions()) {
200
+ webpexpress_migrate1_deleteOldOptions();
201
+ webpexpress_migrate1_deleteOldWebPImages();
202
+ update_option('webp-express-migration-version', '1');
203
+ }
204
+ }
205
+ }
lib/options.php DELETED
@@ -1,498 +0,0 @@
1
- <?php
2
-
3
- // Maybe go away from using Settings API ?
4
- // https://wpshout.com/wordpress-options-page/
5
-
6
-
7
- include_once 'helpers.php';
8
-
9
- /*
10
- These lines should enable us to know whether quality can be detected.
11
- But needs testing...
12
- require WEBPEXPRESS_PLUGIN_DIR . '/vendor/require-webp-convert.php';
13
- $detectedQualityOfTestJpg = \WebPConvert\Converters\ConverterHelper::detectQualityOfJpg(WEBPEXPRESS_PLUGIN_DIR . '/test/focus.jpg');
14
- $canDetectQualityOfJpegs = ($detectedQualityOfTestJpg == 100);
15
- */
16
-
17
- add_action('admin_enqueue_scripts', function () {
18
- // https://github.com/RubaXa/Sortable
19
-
20
- wp_register_script('sortable', plugins_url('../js/sortable.min.js', __FILE__), [], '1.9.0');
21
- wp_enqueue_script('sortable');
22
-
23
- wp_register_script(
24
- 'webp-express-options-page',
25
- plugins_url('../js/webp-express-options-page.js', __FILE__),
26
- ['sortable'],
27
- '0.2.0'
28
- );
29
- wp_enqueue_script('webp-express-options-page');
30
-
31
- wp_add_inline_script('webp-express-options-page', 'window.webpExpressPaths = ' . json_encode(WebPExpressHelpers::calculateUrlsAndPaths()) . ';');
32
- //wp_add_inline_script('webp-express-options-page', 'window.converters = [{"converter":"imagick","id":"imagick"},{"converter":"cwebp","id":"cwebp"},{"converter":"gd","id":"gd"}];');
33
-
34
- //wp_add_inline_script('webp-express-options-page', 'window.converters = [{"converter":"imagick","id":"imagick"},{"converter":"cwebp","id":"cwebp"},{"converter":"gd","id":"gd"}];');
35
- // ,{"converter":"wpc","options":{"url":"http://","secret":"banana"},"id":"wpc"}
36
-
37
-
38
-
39
- wp_register_style(
40
- 'webp-express-options-page-css',
41
- plugins_url('../css/webp-express-options-page.css', __FILE__),
42
- null,
43
- '0.2.0'
44
- );
45
- wp_enqueue_style('webp-express-options-page-css');
46
-
47
- add_thickbox();
48
- });
49
-
50
-
51
-
52
- add_action('admin_init', 'webp_express_option_group_init');
53
-
54
-
55
- function webp_express_option_group_init()
56
- {
57
- register_setting(
58
- 'webp_express_option_group', // A settings group name. Must exist prior to the register_setting call. This must match the group name in settings_fields()
59
- 'webp_express_max_quality', //The name of an option to sanitize and save.
60
- [
61
- 'type' => 'integer',
62
- 'default' => '85',
63
- 'sanitize_callback' => 'sanitize_text_field',
64
- ]
65
- );
66
- register_setting(
67
- 'webp_express_option_group',
68
- 'webp_express_image_types_to_convert',
69
- [
70
- 'type' => 'integer',
71
- 'default' => '1',
72
- 'sanitize_callback' => 'sanitize_text_field',
73
- ]
74
- );
75
- /*
76
- register_setting(
77
- 'webp_express_option_group',
78
- 'webp_express_method',
79
- [
80
- 'type' => 'integer',
81
- 'default' => '6',
82
- 'sanitize_callback' => 'sanitize_text_field',
83
- ]
84
- );*/
85
- register_setting(
86
- 'webp_express_option_group',
87
- 'webp_express_failure_response',
88
- [
89
- 'type' => 'string',
90
- 'default' => 'original',
91
- 'sanitize_callback' => 'sanitize_text_field',
92
- ]
93
- );
94
- register_setting(
95
- 'webp_express_option_group',
96
- 'webp_express_converters',
97
- [
98
- 'type' => 'string',
99
- // TODO: test on new installation
100
- 'default' => '[{"converter":"cwebp","id":"cwebp"},{"converter":"wpc","id":"wpc"},{"converter":"gd","id":"gd"},{"converter":"imagick","id":"imagick"}]',
101
- //'sanitize_callback' => 'sanitize_text_field',
102
- ]
103
- );
104
-
105
-
106
- add_settings_section('webp_express_conversion_options_section', 'Conversion options', function () {
107
- //echo 'here you set conversion options';
108
- }, 'webp_express_settings_page');
109
-
110
- add_settings_field('webp_express_image_types_to_convert_id', 'Image types to convert', function () {
111
- // bitmask
112
- // 1: JPEGs
113
- // 2: PNG's
114
- // Converting only jpegs is thus "1"
115
- // Converting both jpegs and pngs is (1+2) = 3
116
- $imageTypes = get_option('webp_express_image_types_to_convert');
117
-
118
- echo '<select name="webp_express_image_types_to_convert">';
119
- echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>Do not convert any images!</option>';
120
- echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only convert jpegs</option>';
121
- echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Convert both jpegs and pngs</option>';
122
- echo '</select>';
123
-
124
- //echo '<input type="checkbox" ' . ($types == 1 ? ' checked=checked' : '') . '>';
125
-
126
- }, 'webp_express_settings_page', 'webp_express_conversion_options_section');
127
-
128
- /*
129
- add_settings_field('webp_express_method_id', 'Method (0-6)', function () {
130
- $method = get_option('webp_express_method');
131
- echo "<input type='text' name='webp_express_method' value='" . $method . "' />";
132
- 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>';
133
- }, 'webp_express_settings_page', 'webp_express_conversion_options_section');
134
- */
135
-
136
- add_settings_field('webp_express_failure_response', 'Response on failure', function () {
137
- $failureResponse = get_option('webp_express_failure_response');
138
- echo '<select name="webp_express_failure_response">';
139
- echo '<option value="original"' . ($failureResponse == 'original' ? ' selected' : '') . '>Original image</option>';
140
- echo '<option value="404"' . ($failureResponse == '404' ? ' selected' : '') . '>404</option>';
141
- echo '<option value="report"' . ($failureResponse == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
142
- echo '<option value="report-as-image"' . ($failureResponse == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
143
- echo '</select>';
144
- echo '<p>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</p>';
145
- }, 'webp_express_settings_page', 'webp_express_conversion_options_section');
146
-
147
- add_settings_field('webp_express_max_quality_id', 'Max quality (0-100)', function () {
148
- echo "<input type='text' name='webp_express_max_quality' value='" . get_option('webp_express_max_quality') . "' />";
149
- echo '<p>Converted jpeg images will get same quality as original, but not more than this setting. 85 is recommended for most websites.</p>';
150
- }, 'webp_express_settings_page', 'webp_express_conversion_options_section');
151
-
152
-
153
- /*
154
- public static $CONVERTED_IMAGE = 1;
155
- public static $ORIGINAL = -1;
156
- public static $HTTP_404 = -2;
157
- public static $REPORT_AS_IMAGE = -3;
158
- public static $REPORT = -4;*/
159
-
160
- add_settings_field('webp_express_converters', '', function () {
161
- $converters = get_option('webp_express_converters');
162
- echo '<script>window.converters = ' . get_option('webp_express_converters') . '</script>';
163
-
164
- echo "<input type='text' name='webp_express_converters' value='' />";
165
- }, 'webp_express_settings_page', 'webp_express_conversion_options_section');
166
- }
167
-
168
-
169
- /* Settings Page Content */
170
- function webp_express_settings_page_content()
171
- {
172
- if (!current_user_can('manage_options')) {
173
- wp_die(__('You do not have sufficient permissions to access this page.', 'yasr'));
174
- }
175
- ?>
176
- <div class="wrap">
177
- <h2>WebP Express Settings</h2>
178
-
179
- <?php
180
-
181
- global $wpdb;
182
- //$hasWebPExpressOptionBeenSaved = ($wpdb->get_row( "SELECT * FROM $wpdb->options WHERE option_name = 'webp_express_converters'" ) !== null);
183
- //if (!$hasWebPExpressOptionBeenSaved) {
184
- if (empty(get_option('webp-express-configured'))) {
185
- echo '<div style="background-color: #cfc; padding: 20px; border: 1px solid #ccc">';
186
- echo '<h3>Welcome!<h3>';
187
- echo '<p>The rewrite rules are not active yet. They will be activated the first time you click the "Save settings" button.</p>';
188
- 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>';
189
- echo '</div>';
190
- }
191
-
192
- global $wpdb;
193
- $results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}options WHERE option_id = 1", OBJECT );
194
- ?>
195
- <form action="options.php" method="post">
196
- <?php
197
- settings_fields('webp_express_option_group');
198
- do_settings_sections('webp_express_settings_page');
199
-
200
- //print_r(get_option('plugin_error'));
201
-
202
- //echo '<pre>' . print_r(WebPExpressHelpers::calculateUrlsAndPaths(), true) . '</pre>';
203
- $localConverters = ['cwebp', 'imagick', 'gd'];
204
-
205
- /*
206
- $testResult = WebPExpressHelpers::testConverters($localConverters);
207
- //print_r($testResult);
208
-
209
- if ($testResult['numOperationalConverters'] == 0) {
210
- echo 'Unfortunately, your server is currently not able to convert to webp files by itself. You will need to set up a cloud converter.<br><br>';
211
- foreach ($testResult['results'] as $result) {
212
- echo $result['converter'] . ':' . $result['message'] . '<br>';
213
- }
214
- } else {
215
- //echo 'Your server is able to convert webp files by itself.';
216
- }
217
- if ($testResult['numOperationalConverters'] == 1) {
218
- //
219
- }
220
- */
221
- $extraConverters = [
222
- [
223
- 'converter' => 'ewww',
224
- 'options' => array(
225
- 'key' => 'your api key here',
226
- ),
227
- ]
228
- ];
229
-
230
- $converters = [
231
- [
232
- 'converter' => 'cwebp',
233
- ],
234
- [
235
- 'converter' => 'imagick',
236
- ],
237
- [
238
- 'converter' => 'gd',
239
- ],
240
- [
241
- 'converter' => 'ewww',
242
- 'options' => [
243
- 'key' => 'your api key here',
244
- ],
245
- ]
246
- ];
247
-
248
- /*
249
- http://php.net/manual/en/function.set-include-path.php
250
-
251
-
252
- //exec('/usr/sbin/getsebool -a', $output6, $returnCode5); // ok
253
- //echo 'All se bools: ' . print_r($output6, true) . '. Return code:' . $returnCode5;
254
- */
255
-
256
-
257
-
258
-
259
-
260
- echo '<h2>Converters</h2>';
261
- $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>';
262
-
263
- echo '<p><i>Drag to reorder. The converter on top will be used. Should it fail, the next will be used, etc</i></p>';
264
- // https://github.com/RubaXa/Sortable
265
-
266
- // Empty list of converters. The list will be populated by the javascript
267
- echo '<ul id="converters"></ul>';
268
- ?>
269
- <div id="cwebp" style="display:none;">
270
- <div class="cwebp converter-options">
271
- <h3>cwebp</h3>
272
- <div class="info">
273
- cwebp works by executing the cwebp binary from Google. This should normally be your first choice.
274
- Its best in terms of quality, speed and options.
275
- The only catch is that it requires the exec function to be enabled, and that the webserver user is
276
- allowed to execute the cwebp binary (either at known system locations, or one of the precompiled binaries,
277
- that comes with this library).
278
- If you are on a shared host that doesn't allow that, the second best choice would probably be the wpc cloud converter.
279
- </div>
280
- <h3>cweb options</h3>
281
- <div>
282
- <label for="cwebp_use_nice">Use nice</label>
283
- <input type="checkbox" id="cwebp_use_nice">
284
- <br>Enabling "use nice" saves system resources at the cost of slightly slower conversion
285
-
286
- </div>
287
- <br>
288
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
289
- <!-- <a href="javascript: tb_remove();">close</a> -->
290
- </div>
291
- </div>
292
- <div id="gd" style="display:none;">
293
- <div class="ewww converter-options">
294
- <h3>Gd</h3>
295
- <p>
296
- The gd converter uses the Gd extension to do the conversion. It is per default placed below the cloud converters for two reasons.
297
- Firstly, it does not seem to produce quite as good quality as cwebp.
298
- Secondly, it provides no conversion options, besides quality.
299
- The Gd extension is pretty common, so the main feature of this converter is that it <i>may</i> work out of the box.
300
- This is in contrast to the cloud converters, which requires that the user does some setup.
301
- </p>
302
- <h3>Gd options</h3>
303
- <div class="info">
304
- 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.
305
- </div>
306
- <div>
307
- <label for="gd_skip_pngs">Skip PNGs</label>
308
- <input type="checkbox" id="gd_skip_pngs">
309
- </div>
310
- <br>
311
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
312
- <!-- <a href="javascript: tb_remove();">close</a> -->
313
- </div>
314
- </div>
315
- <div id="imagick" style="display:none;">
316
- <div class="imagick converter-options">
317
- <h3>Imagick</h3>
318
- <p>
319
- imagick would be your last choice. For some reason it produces conversions that are only marginally better than the originals.
320
- See <a href="https://github.com/rosell-dk/webp-convert/issues/43" target="_blank">this issue</a>. But it is fast.
321
- </p>
322
- <h3>Imagick options</h3>
323
- <div class="info">
324
- imagick has no extra options.
325
- </div>
326
- <br>
327
- <!--
328
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
329
- -->
330
- <!-- <a href="javascript: tb_remove();">close</a> -->
331
- </div>
332
- </div>
333
- <div id="ewww" style="display:none;">
334
- <div class="ewww converter-options">
335
- <h3>Ewww</h3>
336
- <p>
337
- <a href="https://ewww.io/" target="_blank">ewww</a> is a cloud service.
338
- It is a decent alternative for those who don't have the technical know-how to install wpc.
339
- ewww is using cwebp to do the conversion, so quality is great.
340
- ewww however only provides one conversion option (quality), and it does not support "auto"
341
- quality (yet - I have requested the feature and the maintainer are considering it).
342
- Also, it is not free. But very cheap. Like in almost free.
343
- </p>
344
- <h3>Ewww options</h3>
345
- <div>
346
- <label for="ewww_key">Key</label>
347
- <input type="text" id="ewww_key" placeholder="Your API key here">
348
- </div>
349
- <br>
350
- <h4>Fallback (optional)</h4>
351
- <div>
352
- <label for="ewww_key_2">key</label>
353
- <input type="text" id="ewww_key_2" placeholder="In case the first one expires...">
354
- </div>
355
- <br>
356
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
357
- <!-- <a href="javascript: tb_remove();">close</a> -->
358
- </div>
359
- </div>
360
- <div id="wpc" style="display:none;">
361
- <div class="wpc converter-options">
362
- <h3>WebPConvert Cloud Service</h3>
363
- wpc is an open source cloud converter based on <a href="https://github.com/rosell-dk/webp-convert" target="_blank">WebPConvert</a>
364
- (this plugin also happens to be based on WebPConvert).
365
- Conversions will of course be slower than cwebp, as images need to go back and forth to the cloud converter.
366
- As images usually just needs to be converted once, the slower conversion
367
- speed is probably acceptable. The conversion quality and options of wpc matches cwebp.
368
- The only catch is that you will need to install the WPC library on a server (or have someone do it for you).
369
- <a href="https://github.com/rosell-dk/webp-convert-cloud-service" target="blank">Visit WPC on github</a>.
370
- If this is a problem, we suggest you turn to ewww.
371
- (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)
372
-
373
- <h3>Options for WebPConvert Cloud Service</h3>
374
- <div>
375
- <label for="wpc_url">URL</label>
376
- <input type="text" id="wpc_url" placeholder="Url to your WPC instance">
377
- </div>
378
-
379
- <div>
380
- <label for="wpc_secret">Secret</label>
381
- <input type="text" id="wpc_secret" placeholder="Secret (must match secret on server side)">
382
- </div>
383
- <br>
384
- <h4>Fallback (optional)</h4>
385
- <p>In case the first is down, the fallback will be used.</p>
386
- <div>
387
- <label for="wpc_url_2">URL</label>
388
- <input type="text" id="wpc_url_2" placeholder="Url to your other WPC instance">
389
- </div>
390
- <div>
391
- <label for="wpc_secret_2">Secret</label>
392
- <input type="text" id="wpc_secret_2" placeholder="Secret (must match secret on server side)">
393
- </div>
394
- <br>
395
- <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
396
- </div>
397
- </div>
398
- <?php
399
- //print_r($urls);
400
- //echo $urls['urls']['webpExpressRoot'];
401
- if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
402
- echo 'Your browser supports webp... So you can test if everything works (including the redirect magic) - using these links:<br>';
403
- $webpExpressRoot = WebPExpressHelpers::calculateUrlsAndPaths()['urls']['webpExpressRoot'];
404
- echo '<a href="' . $webpExpressRoot . '/test/test.jpg" target="_blank">Convert test image</a><br>';
405
- echo '<a href="' . $webpExpressRoot . '/test/test.jpg?debug" target="_blank">Convert test image (show debug)</a><br>';
406
- }
407
- ?>
408
-
409
- <!--
410
- <div id="add-cloud-converter-id" style="display:none;">
411
- <p>
412
- Select cloud converter to add:
413
-
414
- <button onclick="addConverter('ewww')" class="button button-primary" type="button">Add ewww converter</button>
415
- </p>
416
- </div>
417
- <button class="button button-secondary" onclick="addConverterClick()" type="button">Add cloud converter</button>
418
- -->
419
- <?php
420
-
421
-
422
- submit_button('Save settings');
423
- ?>
424
- </form>
425
- </div>
426
-
427
- <?php
428
- }
429
-
430
- // This hook is invoked when a option is changed away from default value
431
- add_action('added_option', function($option_name, $value) {
432
-
433
- // Notice that we use underscore in "webp_express" for the configuration, but dash for other options (such as messages and state)
434
- if (strpos($option_name, 'webp_express') === 0) {
435
-
436
- // Store the fact that webp options has been changed.
437
- // When nobody is using 0.3 or below, we can test on the existence of that option, instead of
438
- // querying the database directly ($hasWebPExpressOptionBeenSaved)
439
- add_option('webp-express-configured', true);
440
-
441
- $rules = WebPExpressHelpers::generateHTAccessRules();
442
- WebPExpressHelpers::insertHTAccessRules($rules);
443
- }
444
- }, 10, 3);
445
-
446
- // This hook is invoked when a option is changed (but not when the old value is the same as its default value)
447
- add_action('updated_option', function($option_name, $old_value, $value) {
448
-
449
- // Notice that we use underscore in "webp_express" for the configuration, but dash for other options (such as messages and state)
450
- if (strpos($option_name, 'webp_express') === 0) {
451
- $rules = WebPExpressHelpers::generateHTAccessRules();
452
- WebPExpressHelpers::insertHTAccessRules($rules);
453
- }
454
- }, 10, 3);
455
-
456
-
457
- //End webp_express_settings_page_content
458
-
459
- //include( plugin_dir_path( __FILE__ ) . 'lib/helpers.php');
460
-
461
- //echo '<pre>rules:' . WebPExpressHelpers::generateHTAccessRules() . '</pre>';
462
-
463
- /*
464
- add_action('admin_menu', function () {
465
- add_options_page('WebP Express', 'WebP Express', 'manage_options', 'webp-express', function () {
466
- include(plugin_dir_path(__FILE__) . 'lib/options.php');
467
- });
468
- });
469
-
470
- function d1() {
471
- echo '<p>Main description of this section here.</p>';
472
- }
473
- function d2() {
474
- $options = get_option('options');
475
- echo "<input id='plugin_text_string' name='plugin_options[text_string]' size='40' type='text' value='{$options['text_string']}' />";
476
- }
477
- function plugin_options_validate($input) {
478
- $newinput['text_string'] = trim($input['text_string']);
479
- if (!preg_match('/^[a-z0-9]{32}$/i', $newinput['text_string'])) {
480
- $newinput['text_string'] = '';
481
- }
482
- return $newinput;
483
- }
484
-
485
- add_action('admin_init', function () {
486
- register_setting('general', 'quality', [
487
- 'type' => 'string',
488
- 'default' => '85'
489
- ]);
490
- add_settings_field('plugin_text_string', 'Plugin Text Input', function () {
491
- echo 'hello...';
492
- }, 'webp-express');
493
-
494
- //register_setting('webp_express_options', 'options', 'plugin_options_validate');
495
- //add_settings_section('webp_express_main', 'Main Settings', d1, 'webp_express');
496
- //add_settings_field('plugin_text_string', 'Plugin Text Input', d2, 'webp_express', 'webp_express_main');
497
- });
498
- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{css → lib/options/css}/webp-express-options-page.css RENAMED
@@ -1,13 +1,23 @@
1
- input[name=webp_express_converters] {
2
  display: none;
3
  width: 100%;
 
 
 
 
 
 
4
  }
5
 
 
 
 
6
  /* remove the padding on the row with the hidden "webp_express_converters" setting.
7
  it is the last row, we happen to know... */
 
8
  .form-table tr:last-of-type > * {
9
  padding: 0;
10
- }
11
 
12
 
13
  #converters li {
1
+ /*input[name=webp_express_converters] {
2
  display: none;
3
  width: 100%;
4
+ }*/
5
+
6
+ /* break long lines in pre (when we are showing .htaccess inside notice) */
7
+ .notice pre {
8
+ white-space: pre-wrap;
9
+ word-wrap: break-word;
10
  }
11
 
12
+
13
+
14
+
15
  /* remove the padding on the row with the hidden "webp_express_converters" setting.
16
  it is the last row, we happen to know... */
17
+ /*
18
  .form-table tr:last-of-type > * {
19
  padding: 0;
20
+ }*/
21
 
22
 
23
  #converters li {
lib/options/enqueue_scripts.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/../classes/Paths.php';
4
+ use \WebPExpress\Paths;
5
+
6
+ wp_register_script('sortable', plugins_url('js/sortable.min.js', __FILE__), [], '1.9.0');
7
+ wp_enqueue_script('sortable');
8
+
9
+ wp_register_script(
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
+
17
+ if (function_exists('wp_add_inline_script')) {
18
+ // wp_add_inline_script is available from Wordpress 4.5
19
+ wp_add_inline_script('webp-express-options-page', 'window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';');
20
+ } else {
21
+ echo '<script>window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';</script>';
22
+ }
23
+
24
+ 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
+
32
+ add_thickbox();
{images → lib/options/images}/drag-reorder.svg RENAMED
File without changes
{js → lib/options/js}/sortable.min.js RENAMED
File without changes
{js → lib/options/js}/webp-express-options-page.js RENAMED
@@ -1,34 +1,4 @@
1
 
2
- /*window.converters = [
3
- {
4
- 'converter': 'imagick',
5
- },
6
- {
7
- 'converter': 'cwebp',
8
- },
9
- {
10
- 'converter': 'gd',
11
- },
12
- {
13
- 'converter': 'ewww',
14
- 'options': {
15
- 'key': 'your api key here',
16
- },
17
- },
18
- {
19
- 'converter': 'ewww',
20
- 'options': {
21
- 'key': 'your api key here 2',
22
- },
23
- },
24
- {
25
- 'converter': 'wpc',
26
- 'options': {
27
- 'url': 'http://',
28
- 'secret': 'banana',
29
- },
30
- }
31
- ];*/
32
 
33
  // Map of converters (are updated with updateConvertersMap)
34
  window.convertersMap = {};
@@ -98,17 +68,6 @@ html += '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox=
98
  html += '<a class="deactivate-converter btn" onclick=deactivateConverter(\'' + converter['id'] + '\')>deactivate</a>';
99
  }
100
 
101
- /*
102
- switch (converter['converter']) {
103
- case 'ewww':
104
- case 'wpc':
105
- html += '<a class="remove-converter btn" onclick=removeConverter(\'' + converter['id'] + '\')>remove</a>';
106
- break;
107
- case 'cwebp':
108
- case 'imagick':
109
- case 'gd':
110
- break;
111
- }*/
112
  html += '</li>';
113
  return html;
114
  }
@@ -154,7 +113,7 @@ function reorderConverters(order) {
154
 
155
  /* Update the hidden input containing all the data */
156
  function updateInputValue() {
157
- document.getElementsByName('webp_express_converters')[0].value = JSON.stringify(window.converters);
158
  }
159
 
160
  function setConvertersHTML() {
@@ -276,7 +235,8 @@ function testConverter(id) {
276
  var urls = window.webpExpressPaths['urls'];
277
  var paths = window.webpExpressPaths['filePaths'];
278
 
279
- var url = urls['webpExpressRoot'] + '/test-run.php';
 
280
 
281
 
282
  // test images here: http://nottinghamtec.co.uk/~aer/TestPatterns/1080/
@@ -287,10 +247,9 @@ function testConverter(id) {
287
  filename = 'focus.jpg';
288
 
289
  url += '?source=' + paths['webpExpressRoot'] + '/test/' + filename;
290
- url += '&destination=' + paths['destinationRoot'] + '/' + filename + '.webp';
291
- url += '&destinationUrl=' + urls['destinationRoot'] + '/' + filename + '.webp';
292
  url += '&converter=' + converter['converter'];
293
- url += '&max-quality=' + document.getElementsByName('webp_express_max_quality')[0].value;
294
  //url += '&method=' + document.getElementsByName('webp_express_method')[0].value;
295
 
296
  if (converter.options) {
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  // Map of converters (are updated with updateConvertersMap)
4
  window.convertersMap = {};
68
  html += '<a class="deactivate-converter btn" onclick=deactivateConverter(\'' + converter['id'] + '\')>deactivate</a>';
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
 
71
  html += '</li>';
72
  return html;
73
  }
113
 
114
  /* Update the hidden input containing all the data */
115
  function updateInputValue() {
116
+ document.getElementsByName('converters')[0].value = JSON.stringify(window.converters);
117
  }
118
 
119
  function setConvertersHTML() {
235
  var urls = window.webpExpressPaths['urls'];
236
  var paths = window.webpExpressPaths['filePaths'];
237
 
238
+ var url = '/' + urls['webpExpressRoot'] + '/test/test-run.php';
239
+ //alert(url);
240
 
241
 
242
  // test images here: http://nottinghamtec.co.uk/~aer/TestPatterns/1080/
247
  filename = 'focus.jpg';
248
 
249
  url += '?source=' + paths['webpExpressRoot'] + '/test/' + filename;
250
+ url += '&destination=' + paths['destinationRoot'] + '/test-conversions/' + filename + '.webp';
 
251
  url += '&converter=' + converter['converter'];
252
+ url += '&max-quality=' + document.getElementsByName('max-quality')[0].value;
253
  //url += '&method=' + document.getElementsByName('webp_express_method')[0].value;
254
 
255
  if (converter.options) {
lib/options/options-hooks.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ add_action( 'admin_menu', function() {
4
+
5
+ //Add Settings Page
6
+ add_options_page(
7
+ 'WebP Express Settings', //Page Title
8
+ 'WebP Express', //Menu Title
9
+ 'manage_options', //capability
10
+ 'webp_express_settings_page', //menu slug
11
+ 'webp_express_settings_page_content' //The function to be called to output the content for this page.
12
+ );
13
+ });
14
+
15
+ add_action('admin_post_webpexpress_settings_submit', function() {
16
+ include __DIR__ . '/submit.php';
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
+ });
24
+ }
25
+
26
+ function webp_express_settings_page_content()
27
+ {
28
+ include __DIR__ . '/page.php';
29
+ }
lib/options/page-messages.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use \WebPExpress\Paths;
4
+ use \WebPExpress\HTAccess;
5
+ use \WebPExpress\Config;
6
+ use \WebPExpress\State;
7
+ 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
+ }
14
+
15
+ if (PlatformInfo::definitelyNotGotModRewrite()) {
16
+ Messenger::printMessage(
17
+ 'error',
18
+ "Rewriting isn't enabled on your server. WebP Express cannot work without it. Tell your host or system administrator to enable the 'mod_rewrite' module. If you are on a shared host, chances are that mod_rewrite can be turned on in your control panel."
19
+ );
20
+ }
21
+ /*
22
+ if (Config::isConfigFileThereAndOk() ) { // && PlatformInfo::definitelyGotModEnv()
23
+ if (!isset($_SERVER['HTACCESS'])) {
24
+ Messenger::printMessage(
25
+ 'warning',
26
+ "Using rewrite rules in <i>.htaccess</i> files seems to be disabled " .
27
+ "(The <i>AllowOverride</i> directive is probably set to <i>None</i>. " .
28
+ "It needs to be set to <i>All</i>, or at least <i>FileInfo</i> to allow rewrite rules in <i>.htaccess</i> files.)<br>" .
29
+ "Disabled <i>.htaccess</i> files is actually a good thing, both performance-wise and security-wise. <br> " .
30
+ "But it means you will have to insert the following rules into your apache configuration manually:" .
31
+ "<pre>" . htmlentities(print_r(Config::generateHTAccessRulesFromConfigFile(), true)) . "</pre>"
32
+ );
33
+ }
34
+ }*/
35
+ if (!Paths::createContentDirIfMissing()) {
36
+ Messenger::printMessage(
37
+ 'error',
38
+ 'WebP Express needs to create a directory "webp-express" under your wp-content folder, but does not have permission to do so.<br>' .
39
+ 'Please create the folder manually, or change the file permissions of your wp-content folder.'
40
+ );
41
+ } else {
42
+ if (!Paths::createConfigDirIfMissing()) {
43
+ Messenger::printMessage(
44
+ 'error',
45
+ 'WebP Express needs to create a directory "webp-express/config" under your wp-content folder, but does not have permission to do so.<br>' .
46
+ 'Please create the folder manually, or change the file permissions.'
47
+ );
48
+ }
49
+
50
+ if (!Paths::createCacheDirIfMissing()) {
51
+ Messenger::printMessage(
52
+ 'error',
53
+ 'WebP Express needs to create a directory "webp-express/webp-images" under your wp-content folder, but does not have permission to do so.<br>' .
54
+ 'Please create the folder manually, or change the file permissions.'
55
+ );
56
+ }
57
+ }
58
+
59
+ if (Config::isConfigFileThere()) {
60
+ if (!Config::isConfigFileThereAndOk()) {
61
+ Messenger::printMessage(
62
+ 'warning',
63
+ 'Warning: The configuration file is not ok! (cant be read, or not valid json).<br>' .
64
+ 'file: "' . Paths::getConfigFileName() . '"'
65
+ );
66
+ } else {
67
+ if (HTAccess::arePathsUsedInHTAccessOutdated()) {
68
+ Messenger::printMessage(
69
+ 'warning',
70
+ 'Warning: Wordpress paths have changed since the last time the Rewrite Rules was generated. The rules needs updating! (click <i>Save settings</i> to do so)<br>'
71
+ );
72
+ }
73
+ }
74
+ }
75
+
76
+ if (
77
+ HTAccess::haveWeRulesInThisHTAccessBestGuess(Paths::getIndexDirAbs() . '/.htaccess') &&
78
+ HTAccess::haveWeRulesInThisHTAccessBestGuess(Paths::getWPContentDirAbs() . '/.htaccess')
79
+ ) {
80
+ if (!HTAccess::saveHTAccessRulesToFile(Paths::getIndexDirAbs() . '/.htaccess', '# WebP Express has placed its rules in your wp-content dir. Go there.', false)) {
81
+ Messenger::printMessage(
82
+ 'warning',
83
+ 'Warning: WebP Express have rules in both your wp-content folder and in your Wordpress folder.<br>' .
84
+ 'Please remove those in the <i>.htaccess</i> in your Wordress folder manually, or let us handle it, by granting us write access'
85
+ );
86
+ }
87
+ }
lib/options/page-welcome.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use \WebPExpress\Paths;
4
+ 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();
12
+ $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, ';
25
+ echo '<i>WebP Express</i> would be needing to store the rewrite rules in a <i>.htaccess</i> file in your <i>wp-content</i> directory ';
26
+ echo '(we need to store them there rather than in your root, because you have moved your wp-content folder out of the Wordpress root). ';
27
+ echo 'Please adjust the file permissions of your <i>wp-content</i> dir. ';
28
+
29
+ if (Paths::isPluginDirMovedOutOfWpContent()) {
30
+ echo '<br>But that is not all. Besides moving your wp-content dir, you have <i>also</i> moved your plugin dir... ';
31
+ echo 'If you want WebP-Express to work on the images delivered by your plugins, you must also grant write access to your plugin dir (you can revoke the access after we have written the rules).<br>';
32
+ }
33
+ echo 'You can reload this page aftewards, and this message should be gone</p>';
34
+ } else {
35
+ if (Paths::isPluginDirMovedOutOfWpContent()) {
36
+ echo '<p><b>Oh, one more thing</b>. I can see that your plugin dir has been moved out of your wp-content folder. ';
37
+ echo 'If you want WebP-Express to work on the images delivered by your plugins, you must grant write access to your ';
38
+ echo 'plugin dir (you can revoke the access after we have written the rules, but beware that the plugin may need ';
39
+ echo 'access rights again. Some of the options affects the .htaccess rules. And WebP Express also have to remove the rules if the plugin is disabled)';
40
+ }
41
+ }
42
+ if (Paths::isUploadDirMovedOutOfWPContentDir()) {
43
+ if (!Paths::canWriteHTAccessRulesHere($uploadDir)) {
44
+ echo '<p><b>Oh, one more thing</b>. We also need to write rules to your uploads dir (because you have moved it). ';
45
+ echo 'Please grant us write access to your ';
46
+ if (FileHelper::fileExists($uploadDir . '/.htaccess')) {
47
+ echo '<i>.htaccess</i> file in your upload dir';
48
+ } else {
49
+ echo 'upload dir, so we can plant an <i>.htaccess</i> there';
50
+ }
51
+ echo '. Your upload dir is: <i>' . $uploadDir . '</i>. ';
52
+ echo '- Or alternatively, you can leave it be and update the rules manually, whenever they need to be changed. ';
53
+ }
54
+ }
55
+ } else {
56
+ $firstWritable = Paths::returnFirstWritableHTAccessDir([$wpContentDir, $indexDir]);
57
+ if ($firstWritable === false) {
58
+ echo '<p><b>Oh, one more thing</b>. Unless you are going to put the rewrite rules into your configuration manually, ';
59
+ echo '<i>WebP Express</i> would be needing to store the rewrite rules in a <i>.htaccess</i> file. ';
60
+ echo 'However, your current file permissions does not allow that. ';
61
+ echo '<i>WebP Express</i> would prefer to put the rewrite rules into your <i>wp-content</i> folder, but ';
62
+ echo 'will store them in your main <i>.htaccess</i> file if it can write to that, but not your wp-content. ';
63
+ echo '(The preference for storing in wp-content is simply that it minimizes the risk of conflicts with rules from other plugins. ';
64
+ echo 'deeper <i>.htaccess</i> files takes precedence). ';
65
+ echo 'Anyway: Could you please adjust the file permissions of either your main <i>.htaccess</i> file or your wp-content dir?';
66
+ echo 'You can reload this page aftewards, and this message should be gone</p>';
67
+ } else {
68
+ if ($firstWritable != $wpContentDir) {
69
+ echo '<p>Oh, one more thing. Unless you are going to put the rewrite rules into your configuration manually, ';
70
+ echo '<i>WebP Express</i> would be needing to store the rewrite rules in a <i>.htaccess</i> file. ';
71
+ echo 'Your current file permissions <i>does</i> allow us to store rules in your main <i>.htaccess</i> file. ';
72
+ echo 'However, <i>WebP Express</i> would prefer to put the rewrite rules into your <i>wp-content</i> folder. ';
73
+ echo 'Putting them there will minimize the risk of conflict with rules from other plugins, as ';
74
+ echo 'deeper <i>.htaccess</i> files takes precedence. ';
75
+ echo 'If you would like the <i>.htaccess</i> file to be stored in your wp-content folder, please adjust your file permissions. ';
76
+ echo 'You can reload this page aftewards, and this message should be gone</p>';
77
+ }
78
+ }
79
+ if (Paths::isUploadDirMovedOutOfWPContentDir()) {
80
+ if (!Paths::canWriteHTAccessRulesHere($uploadDir)) {
81
+ echo '<p><b>Oh, one more thing</b>. We also need to write rules to your uploads dir (because you have moved it). ';
82
+ echo 'Please grant us write access to your ';
83
+ if (FileHelper::fileExists($uploadDir . '/.htaccess')) {
84
+ echo '<i>.htaccess</i> file in your upload dir';
85
+ } else {
86
+ echo 'upload dir, so we can plant an <i>.htaccess</i> there';
87
+ }
88
+ echo '. Your upload dir is: <i>' . $uploadDir . '</i>. ';
89
+ echo '- Or alternatively, you can leave it and update the rules manually, whenever they need to be changed. ';
90
+ }
91
+ }
92
+ if (Paths::isPluginDirMovedOutOfAbsPath()) {
93
+ if (!Paths::canWriteHTAccessRulesHere($pluginDir)) {
94
+ echo '<p>Oh, one more thing. I see you have moved your plugins dir out of your root. ';
95
+ echo 'If you want WebP-Express to work on the images delivered by your plugins, you must also grant write access ';
96
+ echo 'to your ';
97
+ if (FileHelper::fileExists($pluginDir . '/.htaccess')) {
98
+ echo '<i>.htaccess</i> file in your plugin dir';
99
+ } else {
100
+ echo 'plugin dir, so we can plant an <i>.htaccess</i> there';
101
+ }
102
+ echo ' (you can revoke the access after we have written the rules).';
103
+ echo '</p>';
104
+ }
105
+ }
106
+ }
107
+
108
+ /*
109
+ if(Paths::canWriteHTAccessRulesHere($wpContentDir)) {
110
+
111
+ if ($firstWritable === false) {
112
+ echo 'Actually, WebP Express does not have permission to write to your main <i>.htaccess</i> either. Please fix. Preferably ';
113
+ }
114
+
115
+
116
+ $firstWritable = Paths::returnFirstWritableHTAccessDir([$indexDir, $homeDir]);
117
+ if ($firstWritable === false) {
118
+ echo 'Actually, WebP Express does not have permission to write to your main <i>.htaccess</i> either. Please fix. Preferably ';
119
+ }
120
+ if(Paths::canWriteHTAccessRulesHere($wpContentDir)) {
121
+ echo '<i>WebP Express</i> however does have rights to write to your main <i>.htaccess</i>. It will work too - probably. But to minimize risk of conflict with rules from other plugins, I recommended you to adjust the file permissions to allow us to write to a <i>.htaccess</i> file in your <i>wp-content dir</i>';
122
+ }
123
+ echo '</p>';
124
+ }*/
125
+
126
+ echo '</div>';
lib/options/page.php ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/../classes/Config.php';
4
+ use \WebPExpress\Config;
5
+
6
+ include_once __DIR__ . '/../classes/FileHelper.php';
7
+ use \WebPExpress\FileHelper;
8
+
9
+ include_once __DIR__ . '/../classes/HTAccess.php';
10
+ use \WebPExpress\HTAccess;
11
+
12
+ include_once __DIR__ . '/../classes/Messenger.php';
13
+ use \WebPExpress\Messenger;
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
+
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.');
27
+ }
28
+ ?>
29
+ <div class="wrap">
30
+ <h2>WebP Express Settings</h2>
31
+
32
+ <?php
33
+ include __DIR__ . "/page-messages.php";
34
+
35
+ /*
36
+ foreach (Paths::getHTAccessDirs() as $dir) {
37
+ echo $dir . ':' . (Paths::canWriteHTAccessRulesHere($dir) ? 'writable' : 'not writable') . '<br>';
38
+ //Paths::canWriteHTAccessRulesHere($dir);
39
+ }*/
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
78
+ // 2: PNG's
79
+ // Converting only jpegs is thus "1"
80
+ // Converting both jpegs and pngs is (1+2) = 3
81
+ //$imageTypes = get_option('webp_express_image_types_to_convert');
82
+ $imageTypes = $config['image-types'];
83
+
84
+ echo '<select name="image-types">';
85
+ echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>Do not convert any images!</option>';
86
+ echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only convert jpegs</option>';
87
+ echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Convert both jpegs and pngs</option>';
88
+ 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'];
98
+ echo '<select name="fail">';
99
+ echo '<option value="original"' . ($fail == 'original' ? ' selected' : '') . '>Original image</option>';
100
+ echo '<option value="404"' . ($fail == '404' ? ' selected' : '') . '>404</option>';
101
+ echo '<option value="report"' . ($fail == 'report' ? ' selected' : '') . '>Error report (in plain text)</option>';
102
+ echo '<option value="report-as-image"' . ($fail == 'report-as-image' ? ' selected' : '') . '>Error report as image</option>';
103
+ 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
+
122
+ // Converters
123
+ // --------------------
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/
130
+
131
+
132
+ ?>
133
+ <?php
134
+ /*
135
+ $localConverters = ['cwebp', 'imagick', 'gd'];
136
+ $testResult = WebPExpressHelpers::testConverters($localConverters);
137
+ //print_r($testResult);
138
+
139
+ if ($testResult['numOperationalConverters'] == 0) {
140
+ echo 'Unfortunately, your server is currently not able to convert to webp files by itself. You will need to set up a cloud converter.<br><br>';
141
+ foreach ($testResult['results'] as $result) {
142
+ echo $result['converter'] . ':' . $result['message'] . '<br>';
143
+ }
144
+ } else {
145
+ //echo 'Your server is able to convert webp files by itself.';
146
+ }
147
+ if ($testResult['numOperationalConverters'] == 1) {
148
+ //
149
+ }
150
+ */
151
+
152
+
153
+ /*
154
+ http://php.net/manual/en/function.set-include-path.php
155
+
156
+ //exec('/usr/sbin/getsebool -a', $output6, $returnCode5); // ok
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
167
+ 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
+ <!--
228
+ <button onclick="updateConverterOptions()" class="button button-primary" type="button">Update</button>
229
+ -->
230
+ <!-- <a href="javascript: tb_remove();">close</a> -->
231
+ </div>
232
+ </div>
233
+ <div id="ewww" style="display:none;">
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">
248
+ </div>
249
+ <br>
250
+ <h4>Fallback (optional)</h4>
251
+ <div>
252
+ <label for="ewww_key_2">key</label>
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">
277
+ </div>
278
+
279
+ <div>
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>
287
+ <label for="wpc_url_2">URL</label>
288
+ <input type="text" id="wpc_url_2" placeholder="Url to your other WPC instance">
289
+ </div>
290
+ <div>
291
+ <label for="wpc_secret_2">Secret</label>
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>
305
+ </form>
306
+ </div>
lib/options/submit.php ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/../classes/Config.php';
4
+ use \WebPExpress\Config;
5
+
6
+
7
+ include_once __DIR__ . '/../classes/HTAccess.php';
8
+ use \WebPExpress\HTAccess;
9
+
10
+ include_once __DIR__ . '/../classes/Messenger.php';
11
+ use \WebPExpress\Messenger;
12
+
13
+ include_once __DIR__ . '/../classes/Paths.php';
14
+ use \WebPExpress\Paths;
15
+
16
+
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']);
31
+ }
32
+
33
+ $result = Config::saveConfigurationAndHTAccess($config, isset($_POST['force']));
34
+
35
+ /*
36
+ Messenger::addMessage(
37
+ 'info',
38
+ isset($_POST['force']) ? 'force' : 'no-force' .
39
+ (HTAccess::doesRewriteRulesNeedUpdate($config) ? 'need' : 'no need')
40
+ );*/
41
+
42
+ /*
43
+ Messenger::addMessage(
44
+ 'info',
45
+ '<pre>' . htmlentities(print_r($result, true)) . '</pre>'
46
+ );*/
47
+
48
+ if (!$result['saved-both-config']) {
49
+ if (!$result['saved-main-config']) {
50
+ Messenger::addMessage(
51
+ 'error',
52
+ 'Failed saving configuration file.<br>' .
53
+ 'Current file permissions are preventing WebP Express to save configuration to: "' . Paths::getConfigFileName() . '"'
54
+ );
55
+ } else {
56
+ Messenger::addMessage(
57
+ 'error',
58
+ 'Failed saving options file. Check file permissions<br>' .
59
+ 'Tried to save to: "' . Paths::getWodOptionsFileName() . '"'
60
+ );
61
+
62
+ }
63
+ } else {
64
+ if (!$result['rules-needed-update']) {
65
+ Messenger::addMessage(
66
+ 'success',
67
+ 'Configuration saved. Rewrite rules did not need to be updated. ' . HTAccess::testLinks($config)
68
+ );
69
+ } else {
70
+ $rulesResult = $result['htaccess-result'];
71
+ /*
72
+ 'mainResult' // 'index', 'wp-content' or 'failed'
73
+ 'minRequired' // 'index' or 'wp-content'
74
+ 'pluginToo' // 'yes', 'no' or 'depends'
75
+ 'uploadToo' // 'yes', 'no' or 'depends'
76
+ 'overidingRulesInWpContentWarning' // true if main result is 'index' but we cannot remove those in wp-content
77
+ 'rules' // the rules that were generated
78
+ 'pluginFailed' // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
79
+ 'pluginFailedBadly' // true if plugin failed AND it seems we have rewrite rules there
80
+ 'uploadFailed' // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
81
+ 'uploadFailedBadly' // true if plugin failed AND it seems we have rewrite rules there
82
+ */
83
+ $mainResult = $rulesResult['mainResult'];
84
+ $rules = $rulesResult['rules'];
85
+
86
+ if ($mainResult == 'failed') {
87
+ if ($rulesResult['minRequired'] == 'wp-content') {
88
+ Messenger::addMessage(
89
+ 'error',
90
+ 'Configuration saved, but failed saving rewrite rules. ' .
91
+ 'Please grant us write access to your <i>wp-content</i> dir (we need that, because you have moved <i>wp-content</i> out of the Wordpress dir) ' .
92
+ '- or, alternatively insert the following rules directly in that <i>.htaccess</i> file, or your Apache configuration:' .
93
+ '<pre>' . htmlentities(print_r($rules, true)) . '</pre>'
94
+ );
95
+
96
+ } else {
97
+ Messenger::addMessage(
98
+ 'error',
99
+ 'Configuration saved, but failed saving rewrite rules. ' .
100
+ 'Please grant us write access to either write rules to an <i>.htaccess</i> in your <i>wp-content</i> dir (preferably), ' .
101
+ 'or your main <i>.htaccess</i> file. ' .
102
+ '- or, alternatively insert the following rules directly in that <i>.htaccess</i> file, or your Apache configuration:' .
103
+ '<pre>' . htmlentities(print_r($rules, true)) . '</pre>'
104
+ );
105
+ }
106
+ } else {
107
+ $savedToPluginsToo = (($rulesResult['pluginToo'] == 'yes') && !($rulesResult['pluginFailed']));
108
+ $savedToUploadsToo = (($rulesResult['uploadToo'] == 'yes') && !($rulesResult['uploadFailed']));
109
+
110
+ Messenger::addMessage(
111
+ 'success',
112
+ 'Configuration saved. Rewrite rules were saved to your <i>.htaccess</i> in your <i>' . $mainResult . '</i> folder' .
113
+ (Paths::isWPContentDirMoved() ? ' (which you moved, btw)' : '') .
114
+ ($savedToPluginsToo ? ' as well as in your <i>plugins</i> folder' : '') .
115
+ ((Paths::isWPContentDirMoved() && $savedToPluginsToo) ? ' (you moved that as well!)' : '.') .
116
+ ($savedToUploadsToo ? ' as well as in your <i>uploads</i> folder' : '') .
117
+ ((Paths::isWPContentDirMoved() && $savedToUploadsToo) ? ' (you moved that as well!)' : '.') .
118
+ HTAccess::testLinks($config)
119
+ );
120
+ }
121
+ if ($rulesResult['mainResult'] == 'index') {
122
+ if ($rulesResult['overidingRulesInWpContentWarning']) {
123
+ Messenger::addMessage(
124
+ 'warning',
125
+ 'We have rewrite rules in the <i>wp-content</i> folder, which we cannot remove. ' .
126
+ 'These are overriding those just saved. ' .
127
+ 'Please change file permissions or remove the rules from the <i>.htaccess</i> file manually'
128
+ );
129
+ } else {
130
+ Messenger::addMessage(
131
+ 'info',
132
+ 'The rewrite rules are currently stored in your root. ' .
133
+ 'WebP Express would prefer to store them in your wp-content folder, ' .
134
+ 'but your current file permissions does not allow that.'
135
+ );
136
+ }
137
+ }
138
+ if ($rulesResult['pluginFailed']) {
139
+ if ($rulesResult['pluginFailedBadly']) {
140
+ Messenger::addMessage(
141
+ 'warning',
142
+ 'The <i>.htaccess</i> rules in your plugins folder could not be updated (no write access). ' .
143
+ 'This is not so good, because we have rules there already...' .
144
+ 'You should update them. Here they are: ' .
145
+ '<pre>' . htmlentities(print_r($rules, true)) . '</pre>'
146
+ );
147
+ } else {
148
+ Messenger::addMessage(
149
+ 'info',
150
+ '<i>.htaccess</i> rules could not be written into your plugins folder. ' .
151
+ 'Images stored in your plugins will not be converted to webp'
152
+ );
153
+ }
154
+ }
155
+ if ($rulesResult['uploadFailed']) {
156
+ if ($rulesResult['uploadFailedBadly']) {
157
+ Messenger::addMessage(
158
+ 'error',
159
+ 'The <i>.htaccess</i> rules in your uploads folder could not be updated (no write access). ' .
160
+ 'This is not so good, because we have rules there already...' .
161
+ 'You should update them. Here they are: ' .
162
+ '<pre>' . htmlentities(print_r($rules, true)) . '</pre>'
163
+ );
164
+ } else {
165
+ Messenger::addMessage(
166
+ 'warning',
167
+ '<i>.htaccess</i> rules could not be written into your uploads folder (this is needed, because you have moved it outside your <i>wp-content</i> folder). ' .
168
+ 'Please grant write permmissions to you uploads folder. Otherwise uploaded mages will not be converted to webp'
169
+ );
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ wp_redirect( $_SERVER['HTTP_REFERER']);
176
+
177
+ exit();
lib/reactivate.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ include_once __DIR__ . '/classes/Config.php';
4
+ use \WebPExpress\Config;
5
+
6
+ include_once __DIR__ . '/classes/HTAccess.php';
7
+ use \WebPExpress\HTAccess;
8
+
9
+ include_once __DIR__ . '/classes/Messenger.php';
10
+ use \WebPExpress\Messenger;
11
+
12
+ include_once __DIR__ . '/classes/Actions.php';
13
+ use \WebPExpress\Actions;
14
+
15
+ include_once __DIR__ . '/classes/PlatformInfo.php';
16
+ use \WebPExpress\PlatformInfo;
17
+
18
+ include_once __DIR__ . '/classes/State.php';
19
+ use \WebPExpress\State;
20
+
21
+
22
+ // The plugin has been reactivated.
23
+ // We must regenerate the .htaccess rules.
24
+ // (config dir and options and of course still there, no need to do anything about that)
25
+
26
+ // Messenger::addMessage('error', 'You are on Microsof IIS server. The plugin does not work on IIS (yet)');
27
+ //Actions::procastinate('deactivate');
28
+
29
+ $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);
37
+ /*
38
+ 'mainResult' // 'index', 'wp-content' or 'failed'
39
+ 'minRequired' // 'index' or 'wp-content'
40
+ 'pluginToo' // 'yes', 'no' or 'depends'
41
+ 'pluginFailed' // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
42
+ 'pluginFailedBadly' // true if plugin failed AND it seems we have rewrite rules there
43
+ 'overidingRulesInWpContentWarning' // true if main result is 'index' but we cannot remove those in wp-content
44
+ 'rules' // the rules that were generated
45
+ */
46
+ $mainResult = $rulesResult['mainResult'];
47
+ $rules = $rulesResult['rules'];
48
+
49
+ if ($mainResult != 'failed') {
50
+ Messenger::addMessage(
51
+ 'success',
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
+
66
+ }
67
+
68
+ }
lib/uninstall.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //include_once __DIR__ . '/classes/Config.php';
4
+ //use \WebPExpress\Config;
5
+
6
+ include_once __DIR__ . '/classes/Paths.php';
7
+ use \WebPExpress\Paths;
8
+
9
+
10
+ /* helper. Remove dir recursively. No warnings - fails silently */
11
+ function webpexpress_rrmdir($dir) {
12
+ if (@is_dir($dir)) {
13
+ $objects = @scandir($dir);
14
+ foreach ($objects as $object) {
15
+ if ($object != "." && $object != "..") {
16
+ if (@is_dir($dir."/".$object))
17
+ webpexpress_rrmdir($dir."/".$object);
18
+ else
19
+ @unlink($dir."/".$object);
20
+ }
21
+ }
22
+ @rmdir($dir);
23
+ }
24
+ }
25
+
26
+ $optionsToDelete = [
27
+ 'webp-express-messages-pending',
28
+ 'webp-express-action-pending',
29
+ 'webp-express-state',
30
+ 'webp-express-version',
31
+ 'webp-express-activation-error',
32
+ 'webp-express-migration-version'
33
+ ];
34
+ foreach ($optionsToDelete as $i => $optionName) {
35
+ delete_option($optionName);
36
+ }
37
+
38
+ // remove content dir (config plus images)
39
+ webpexpress_rrmdir(Paths::getContentDirAbs());
test-run.php DELETED
@@ -1,260 +0,0 @@
1
- <?php
2
-
3
- //require 'webp-on-demand/vendor/autoload.php';
4
-
5
-
6
- //require 'vendor/webp-convert-and-serve/autoload.php';
7
- //require 'vendor/webp-convert/autoload.php';
8
-
9
- error_reporting(E_ALL);
10
- ini_set("display_errors", 1);
11
-
12
- //require 'vendor/webp-convert/require-all.inc';
13
- require 'vendor/require-webp-convert.php';
14
-
15
- //use WebPConvertAndServe\WebPConvertAndServe;
16
- use WebPConvert\WebPConvert;
17
- use WebPConvert\Converters\ConverterHelper;
18
-
19
- // TODO:
20
- // Much of this file could be moved into the libraries.
21
- // Ie:
22
- // - Report (such as "trying gd", "successfully...", "file size (original)") could be part of WebPConvertAndServe
23
- // ($REPORT_AS_IMAGE and $REPORT actions could show complete report, and convertAndReport too)
24
- // - or even be part of WebPConvert - ie the log could be returned in a variable passed by reference.
25
- //
26
-
27
- $source = $_GET['source'];
28
- $destination = $_GET['destination'];
29
- $converter = $_GET['converter'];
30
-
31
- if (isset($_GET['max-quality'])) {
32
- $options['max-quality'] = intval($_GET['max-quality']);
33
- }
34
-
35
- if (isset($_GET['method'])) {
36
- $options['method'] = intval($_GET['method']);
37
- }
38
-
39
- /*
40
- switch ($converter) {
41
- case 'ewww':
42
- if (isset($_GET['key'])) {
43
- $options['key'] = $_GET['key'];
44
- }
45
- break;
46
- case 'cwebp':
47
- if (isset($_GET['use-nice'])) {
48
- $options['use-nice'] = boolval($_GET['use-nice'] == 'true');
49
- }
50
- break;
51
- case 'gd':
52
- if (isset($_GET['skip-pngs'])) {
53
- $options['skip-pngs'] = boolval($_GET['skip-pngs'] == 'true');
54
- }
55
- break;
56
- }*/
57
-
58
- $converterClassName = 'WebPConvert\\Converters\\' . ucfirst($converter);
59
- $availOptions = array_column($converterClassName::$extraOptions, 'type', 'name');
60
- //print_r($availOptions);
61
-
62
- $hasFallback = false;
63
- $options2 = [];
64
- foreach ($availOptions as $optionName => $optionType) {
65
- if (isset($_GET[$optionName . '-2'])) {
66
- if ($_GET[$optionName . '-2'] != '') {
67
- $hasFallback = true;
68
- $options2 = $options;
69
- // echo 'value:' . $_GET[$optionName . '-2'];
70
- break;
71
- }
72
- }
73
- }
74
-
75
- foreach ($availOptions as $optionName => $optionType) {
76
- switch ($optionType) {
77
- case 'string':
78
- if (isset($_GET[$optionName])) {
79
- $options[$optionName] = $_GET[$optionName];
80
- }
81
- if (isset($_GET[$optionName. '-2'])) {
82
- $options2[$optionName] = $_GET[$optionName . '-2'];
83
- } else {
84
- if ($hasFallback) {
85
- $options2[$optionName] = $options[$optionName];
86
- }
87
- }
88
- break;
89
- case 'boolean':
90
- if (isset($_GET[$optionName])) {
91
- $options[$optionName] = ($_GET[$optionName] == 'true');
92
- }
93
- break;
94
- }
95
- }
96
-
97
- //echo '<pre>' . print_r($options, true) . '</pre>';
98
- //echo '<pre>' . print_r($options2, true) . '</pre>';
99
-
100
- //echo '';
101
- ?>
102
- <html>
103
- <head>
104
- <style>
105
- body {
106
- padding:10px;
107
- font-size: 17px;
108
- }
109
- p {
110
- margin-top: 0;
111
- }
112
- label {
113
- font-style: italic;
114
- }
115
- p.error-msg {
116
- /*font-size: 20px;*/
117
- }
118
- h3 {color: red}
119
- </style>
120
- </head>
121
- <body style="">
122
-
123
- <?php
124
- //echo '<p>source: ' . $source . '</p>';
125
- //echo '<p>destination: ' . $destination . '</p>';
126
- //echo '<p>converter: ' . $converter . '</p>';
127
- //echo '</body></html>';
128
-
129
- //WebPConvertAndServe::convertAndReport($source, $destination, $options);
130
-
131
- function testRun($converter, $source, $destination, $options) {
132
- $beginTime = microtime(true);
133
-
134
- try {
135
- ConverterHelper::runConverter($converter, $source, $destination, $options);
136
- } catch (\WebPConvert\Exceptions\WebPConvertBaseException $e) {
137
- $failure = $e->description;
138
- $msg = $e->getMessage();
139
- } catch (\Exception $e) {
140
- $failure = 'Unancipated failure';
141
- $msg = $e->getMessage();
142
- }
143
-
144
- $endTime = microtime(true);
145
- $duration = $endTime - $beginTime;
146
-
147
- if (isset($msg)) {
148
- echo '<h3 class="error">Test conversion failed (in ' . round($duration * 1000) . ' ms)</h3>';
149
- echo '<label>Problem:</label>';
150
- echo '<p class="failure">' . $failure . '</p>';
151
- echo '<label>Details:</label>';
152
- echo '<p class="error-msg">' . $msg . '</p>';
153
- } else {
154
- echo '<p>Successfully converted test image in ' . round($duration * 1000) . ' ms</p>';
155
-
156
- if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
157
- echo '<img src="' . $_GET['destinationUrl'] . '" width=48%><br><br>';
158
- }
159
- if (filesize($source) < 10000) {
160
- echo 'file size (original): ' . round(filesize($source)) . ' bytes<br>';
161
- echo 'file size (converted): ' . round(filesize($destination)) . ' bytes<br>';
162
- }
163
- else {
164
- echo 'file size (original): ' . round(filesize($source)/1000) . ' kb<br>';
165
- echo 'file size (converted): ' . round(filesize($destination)/1000) . ' kb<br>';
166
- }
167
- }
168
- }
169
-
170
- testRun($converter, $source, $destination, $options);
171
-
172
- if ($hasFallback) {
173
- echo '<h2>Testing fallback</h2>';
174
- testRun($converter, $source, $destination, $options2);
175
- }
176
-
177
-
178
-
179
-
180
- /*
181
- $className = 'WebPConvert\\Converters\\' . ucfirst($converter);
182
-
183
- if (!is_callable([$className, 'convert'])) {
184
- echo 'Converter does not appear to exist!';
185
- exit;
186
- }
187
-
188
- try {
189
- call_user_func(
190
- [$className, 'convert'],
191
- $source,
192
- $destination,
193
- $options
194
- );
195
- } catch (\WebPConvert\Converters\Exceptions\ConverterNotOperationalException $e) {
196
- // The converter is not operational.
197
- $failure = 'The converter is not operational';
198
-
199
- // TODO: We should show link to install instructions for the specific converter (WIKI)
200
-
201
- $msg = $e->getMessage();
202
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
203
- $failure = 'The converter failed converting, although requirements seemed to be met';
204
- $msg = $e->getMessage();
205
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
206
- $failure = 'The converter declined converting';
207
- $msg = $e->getMessage();
208
- } catch (\WebPConvert\Exceptions\InvalidFileExtensionException $e) {
209
- $failure = 'The converter does not accept the file extension';
210
- $msg = $e->getMessage();
211
- } catch (\WebPConvert\Exceptions\TargetNotFoundException $e) {
212
- $failure = 'The converter could not locate source file';
213
- $msg = $e->getMessage();
214
- } catch (\WebPConvert\Exceptions\CreateDestinationFolderException $e) {
215
- $failure = 'The converter could not create destination folder. Check file permisions!';
216
- $msg = $e->getMessage();
217
- } catch (\WebPConvert\Exceptions\CreateDestinationFileException $e) {
218
- $failure = 'The converter could not create destination file. Check file permisions!';
219
- $msg = $e->getMessage();
220
- } catch (\Exception $e) {
221
- $failure = 'Unexpected failure';
222
- $msg = $e->getMessage();
223
- }
224
- */
225
-
226
-
227
-
228
-
229
- /*
230
- try {
231
- $options = [
232
- 'converters' => [$converter]
233
- ];
234
- $success = WebPConvert::convert($source, $destination, $options);
235
- } catch (\Exception $e) {
236
- $success = false;
237
- $msg = $e->getMessage();
238
- }
239
-
240
- if ($success) {
241
- $endTime = microtime(true);
242
-
243
- $duration = $endTime - $beginTime;
244
- echo '<p>Successfully converted test image in ' . round($duration * 1000) . ' ms</p>';
245
-
246
-
247
- if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
248
- echo '<img src="' . $_GET['destinationUrl'] . '" width=50%>';
249
- }
250
- } else {
251
- echo 'Converter failed ';
252
- echo $msg;
253
- }
254
- */
255
- /*
256
- $status = WebPOnDemand::serve(__DIR__);
257
- if ($status < 0) {
258
- // Conversion failed.
259
- // you could message your application about the problem here...
260
- }*/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test/test-run.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (isset($_GET['stream-webp-image'])) {
4
+ header('Content-type: image/webp');
5
+ if (@readfile($_GET['stream-webp-image']) === false) {
6
+ // ...
7
+ }
8
+ }
9
+
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;
19
+ //use WebPConvertAndServe\WebPConvertAndServe;
20
+ //use WebPConvert\Converters\ConverterHelper;
21
+ //use WebPConvertAndServe;
22
+
23
+ ?>
24
+ <html>
25
+ <head>
26
+ <style>
27
+ body {
28
+ padding:10px;
29
+ font-size: 17px;
30
+ }
31
+ p {
32
+ margin-top: 0;
33
+ }
34
+ label {
35
+ font-style: italic;
36
+ }
37
+ p.error-msg {
38
+ /*font-size: 20px;*/
39
+ }
40
+ h3 {color: red}
41
+ </style>
42
+ </head>
43
+ <body style="">
44
+
45
+ <?php
46
+ //WebPConvertAndServe::convertAndReport($source, $destination, $options);use WebPConvert\Loggers\EchoLogger;
47
+ $source = $_GET['source'];
48
+ $destination = $_GET['destination'];
49
+ $converter = $_GET['converter'];
50
+
51
+ if (isset($_GET['max-quality'])) {
52
+ $options['max-quality'] = intval($_GET['max-quality']);
53
+ }
54
+
55
+ /*
56
+ if (isset($_GET['method'])) {
57
+ $options['method'] = intval($_GET['method']);
58
+ }*/
59
+
60
+ /**
61
+ * Sets the options from the query string
62
+ * We make sure only to set those options that are declared by the converter
63
+ */
64
+ function getConverterOptionsFromQueryString($converter)
65
+ {
66
+
67
+ // Get meta about the options that the converter supports
68
+ $converterClassName = 'WebPConvert\\Converters\\' . ucfirst($converter);
69
+ $availOptions = array_column($converterClassName::$extraOptions, 'type', 'name');
70
+ //print_r($availOptions);
71
+
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');
84
+ }
85
+ break;
86
+ }
87
+ }
88
+ return $options;
89
+ }
90
+ $options['converters'] = [[
91
+ 'converter' => $converter,
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());
102
+ } catch (\Exception $e) {
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>';
114
+ //echo '<p class="failure">' . $failure . '</p>';
115
+ //echo '<label>Details:</label>';
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
+
137
+ testRun($converter, $source, $destination, $options);
vendor/require-webp-convert-and-serve.php DELETED
@@ -1,4 +0,0 @@
1
- <?php
2
- require_once(__DIR__ . "/webp-convert-and-serve/../require-webp-convert.php");
3
- require_once(__DIR__ . "/webp-convert-and-serve/BufferLogger.php");
4
- require_once(__DIR__ . "/webp-convert-and-serve/WebPConvertAndServe.php");
 
 
 
 
vendor/require-webp-convert.php DELETED
@@ -1,20 +0,0 @@
1
- <?php
2
- require_once(__DIR__ . "/webp-convert/Exceptions/WebPConvertBaseException.php");
3
- require_once(__DIR__ . "/webp-convert/Loggers/BaseLogger.php");
4
- require_once(__DIR__ . "/webp-convert/WebPConvert.php");
5
- require_once(__DIR__ . "/webp-convert/Converters/ConverterHelper.php");
6
- require_once(__DIR__ . "/webp-convert/Converters/Cwebp.php");
7
- require_once(__DIR__ . "/webp-convert/Converters/Ewww.php");
8
- require_once(__DIR__ . "/webp-convert/Converters/Gd.php");
9
- require_once(__DIR__ . "/webp-convert/Converters/Imagick.php");
10
- require_once(__DIR__ . "/webp-convert/Converters/Wpc.php");
11
- require_once(__DIR__ . "/webp-convert/Exceptions/ConverterNotFoundException.php");
12
- require_once(__DIR__ . "/webp-convert/Exceptions/CreateDestinationFileException.php");
13
- require_once(__DIR__ . "/webp-convert/Exceptions/CreateDestinationFolderException.php");
14
- require_once(__DIR__ . "/webp-convert/Exceptions/InvalidFileExtensionException.php");
15
- require_once(__DIR__ . "/webp-convert/Exceptions/TargetNotFoundException.php");
16
- require_once(__DIR__ . "/webp-convert/Converters/Exceptions/ConversionDeclinedException.php");
17
- require_once(__DIR__ . "/webp-convert/Converters/Exceptions/ConverterFailedException.php");
18
- require_once(__DIR__ . "/webp-convert/Converters/Exceptions/ConverterNotOperationalException.php");
19
- require_once(__DIR__ . "/webp-convert/Loggers/EchoLogger.php");
20
- require_once(__DIR__ . "/webp-convert/Loggers/VoidLogger.php");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/require-webp-on-demand.php DELETED
@@ -1,3 +0,0 @@
1
- <?php
2
- require_once(__DIR__ . "/webp-on-demand/../require-webp-convert-and-serve.php");
3
- require_once(__DIR__ . "/webp-on-demand/WebPOnDemand.php");
 
 
 
vendor/webp-convert-and-serve/BufferLogger.php DELETED
@@ -1,57 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvertAndServe;
4
- use WebPConvert\Loggers\BaseLogger;
5
-
6
- class BufferLogger extends BaseLogger
7
- {
8
- public $entries = array();
9
-
10
- public function log($msg, $style = '')
11
- {
12
- $this->entries[] = [$msg, $style];
13
- }
14
-
15
- public function ln()
16
- {
17
- $this->entries[] = '';
18
- }
19
-
20
- public function getHtml()
21
- {
22
- $html = '';
23
- foreach ($this->entries as $entry) {
24
- if ($entry == '') {
25
- $html .= '<br>';
26
- } else {
27
- list($msg, $style) = $entry;
28
-
29
- if ($style == 'bold') {
30
- $html .= '<b>' . $msg . '</b>';
31
- } elseif ($style == 'italic') {
32
- $html .= '<i>' . $msg . '</i>';
33
- } else {
34
- $html .= $msg;
35
- }
36
- }
37
- }
38
- return $html;
39
- }
40
-
41
- public function getText($newLineChar = ' ')
42
- {
43
- $text = '';
44
- foreach ($this->entries as $entry) {
45
- if ($entry == '') {
46
- if (substr($text, -2) != '. ') {
47
- $text .= '. ';
48
- }
49
- } else {
50
- list($msg, $style) = $entry;
51
- $text .= $msg;
52
- }
53
- }
54
-
55
- return $text;
56
- }
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert-and-serve/WebPConvertAndServe.php DELETED
@@ -1,254 +0,0 @@
1
- <?php
2
- namespace WebPConvertAndServe;
3
-
4
- use WebPConvert\WebPConvert;
5
- use WebPConvertAndServe\BufferLogger;
6
- use WebPConvert\Converters\ConverterHelper;
7
- //use WebPConvert\Loggers\EchoLogger;
8
-
9
- class WebPConvertAndServe
10
- {
11
- public static $CONVERTED_IMAGE = 1;
12
- public static $ORIGINAL = -1;
13
- public static $HTTP_404 = -2;
14
- public static $REPORT_AS_IMAGE = -3;
15
- public static $REPORT = -4;
16
-
17
- private static function serve404()
18
- {
19
- $protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
20
- header($protocol . " 404 Not Found");
21
- }
22
-
23
- private static function serveOriginal($source)
24
- {
25
- // Prevent caching image
26
- header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
27
- header("Cache-Control: post-check=0, pre-check=0", false);
28
- header("Pragma: no-cache");
29
-
30
- $arr = explode('.', $source);
31
- $ext = array_pop($arr);
32
- switch (strtolower($ext)) {
33
- case 'jpg':
34
- case 'jpeg':
35
- header('Content-type: image/jpeg');
36
- break;
37
- case 'png':
38
- header('Content-type: image/png');
39
- break;
40
- }
41
- readfile($source);
42
- }
43
-
44
- private static function serveErrorMessageImage($msg)
45
- {
46
- // Generate image containing error message
47
- header('Content-type: image/gif');
48
-
49
- // Prevent caching image
50
- header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
51
- header("Cache-Control: post-check=0, pre-check=0", false);
52
- header("Pragma: no-cache");
53
-
54
- $image = imagecreatetruecolor(620, 200);
55
- imagestring($image, 1, 5, 5, $msg, imagecolorallocate($image, 233, 214, 291));
56
- // echo imagewebp($image);
57
- echo imagegif($image);
58
- imagedestroy($image);
59
- }
60
-
61
-
62
- public static function convertAndServeImage($source, $destination, $options, $failAction, $criticalFailAction, $debug = false)
63
- {
64
- if ($debug) {
65
- error_reporting(E_ALL);
66
- ini_set('display_errors', 'On');
67
- } else {
68
- ini_set('display_errors', 'Off');
69
- }
70
-
71
- $criticalFail = false;
72
-
73
- $success = false;
74
-
75
- $bufferLogger = new BufferLogger();
76
-
77
- try {
78
- $success = WebPConvert::convert($source, $destination, $options, $bufferLogger);
79
-
80
- if ($success) {
81
- $status = 'Success';
82
- $msg = 'Success';
83
- } else {
84
- $status = 'Failure (no converters are operational)';
85
- $msg = 'No converters are operational';
86
- }
87
- } catch (\WebPConvert\Exceptions\InvalidFileExtensionException $e) {
88
- $criticalFail = true;
89
- $status = 'Failure (invalid file extension)';
90
- $msg = $e->getMessage();
91
- } catch (\WebPConvert\Exceptions\TargetNotFoundException $e) {
92
- $criticalFail = true;
93
- $status = 'Failure (target file not found)';
94
- $msg = $e->getMessage();
95
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
96
- // No converters could convert the image. At least one converter failed, even though it appears to be operational
97
- $status = 'Failure (no converters could convert the image)';
98
- $msg = $e->getMessage();
99
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
100
- // (no converters could convert the image. At least one converter declined
101
- $status = 'Failure (no converters could/wanted to convert the image)';
102
- $msg = $e->getMessage();
103
- } catch (\WebPConvert\Exceptions\ConverterNotFoundException $e) {
104
- $status = 'Failure (a converter was not found!)';
105
- $msg = $e->getMessage();
106
- } catch (\WebPConvert\Exceptions\CreateDestinationFileException $e) {
107
- $status = 'Failure (cannot create destination file)';
108
- $msg = $e->getMessage();
109
- } catch (\WebPConvert\Exceptions\CreateDestinationFolderException $e) {
110
- $status = 'Failure (cannot create destination folder)';
111
- $msg = $e->getMessage();
112
- } catch (\Exception $e) {
113
- $status = 'Failure (an unanticipated exception was thrown)';
114
- $msg = $e->getMessage();
115
- }
116
-
117
- $optionsForPrint = [];
118
- foreach (self::getPrintableOptions($options) as $optionName => $optionValue) {
119
- if ($optionName == 'converters') {
120
- $converterNames = [];
121
- $extraConvertOptions = [];
122
- foreach ($optionValue as $converter) {
123
- if (is_array($converter)) {
124
- $converterNames[] = $converter['converter'];
125
- if (isset($converter['options'])) {
126
- $extraConvertOptions[$converter['converter']] = $converter['options'];
127
- }
128
- } else {
129
- $converterNames[] = $converter;
130
- }
131
- }
132
- $optionsForPrint[] = 'converters:' . implode(',', $converterNames);
133
- foreach ($extraConvertOptions as $converter => $extraOptions) {
134
- $opt = [];
135
- foreach ($extraOptions as $oName => $oValue) {
136
- $opt[] = $oName . ':"' . $oValue . '"';
137
- }
138
- $optionsForPrint[] = $converter . ' options:(' . implode($opt, ', ') . ')';
139
- }
140
- } else {
141
- $optionsForPrint[] = $optionName . ':' . $optionValue ;
142
- }
143
-
144
- }
145
-
146
- header('X-WebP-Convert-And-Serve-Options:' . implode('. ', $optionsForPrint));
147
-
148
- header('X-WebP-Convert-And-Serve-Status: ' . $status);
149
-
150
- // Next line is commented out, because we need to be absolute sure that the details does not violate header syntax
151
- // We could either try to filter it, or we could change WebPConvert, such that it only provides safe texts.
152
- // header('X-WebP-Convert-And-Serve-Details: ' . $bufferLogger->getText());
153
-
154
- if ($success) {
155
- header('Content-type: image/webp');
156
- // Should we add Content-Length header?
157
- // header('Content-Length: ' . filesize($file));
158
- readfile($destination);
159
- return self::$CONVERTED_IMAGE;
160
- } else {
161
- $action = ($criticalFail ? $criticalFailAction : $failAction);
162
-
163
- switch ($action) {
164
- case WebPConvertAndServe::$ORIGINAL:
165
- self::serveOriginal($source);
166
- break;
167
- case WebPConvertAndServe::$HTTP_404:
168
- self::serve404();
169
- break;
170
- case WebPConvertAndServe::$REPORT_AS_IMAGE:
171
- self::serveErrorMessageImage($status . '. ' . $msg);
172
- break;
173
- case WebPConvertAndServe::$REPORT:
174
- echo '<h1>' . $status . '</h1>';
175
- echo $msg;
176
- echo '<p>This is how conversion process went:</p>' . $bufferLogger->getHtml();
177
- break;
178
- }
179
- return $action;
180
- }
181
- }
182
-
183
- /* Hides sensitive options */
184
- private static function getPrintableOptions($options)
185
- {
186
-
187
- $printable_options = [];
188
-
189
- // (psst: the is_callable check is needed in order to work with WebPConvert v1.0)
190
- if (is_callable('ConverterHelper', 'getClassNameOfConverter')) {
191
-
192
- $printable_options = $options;
193
- if (isset($printable_options['converters'])) {
194
- foreach ($printable_options['converters'] as &$converter) {
195
- if (is_array($converter)) {
196
- //echo '::' . $converter['converter'] . '<br>';
197
- $className = ConverterHelper::getClassNameOfConverter($converter['converter']);
198
-
199
- // (pstt: the isset check is needed in order to work with WebPConvert v1.0)
200
- if (isset($className::$extraOptions)) {
201
- foreach ($className::$extraOptions as $extraOption) {
202
- if ($extraOption['sensitive']) {
203
- if (isset($converter['options'][$extraOption['name']])) {
204
- $converter['options'][$extraOption['name']] = '*******';
205
- }
206
- }
207
- }
208
- }
209
- }
210
- }
211
- }
212
- }
213
- return $printable_options;
214
- }
215
-
216
- public static function convertAndReport($source, $destination, $options)
217
- {
218
- error_reporting(E_ALL);
219
- ini_set('display_errors', 'On');
220
-
221
- echo '<html><style>td {vertical-align: top} table {color: #666}</style>';
222
- echo '<body><table>';
223
- echo '<tr><td><i>source:</i></td><td>' . $source . '</td></tr>';
224
- echo '<tr><td><i>destination:</i></td><td>' . $destination . '<td></tr>';
225
-
226
- echo '<tr><td><i>options:</i></td><td>' . print_r(self::getPrintableOptions($options), true) . '</td></tr>';
227
- echo '</table>';
228
-
229
- // TODO:
230
- // We could display warning if unknown options are set
231
- // but that requires that WebPConvert also describes its general options
232
-
233
- echo '<br>';
234
-
235
- try {
236
- $echoLogger = new \WebPConvert\Loggers\EchoLogger();
237
- $success = WebPConvert::convert($source, $destination, $options, $echoLogger);
238
- } catch (\Exception $e) {
239
- $success = false;
240
-
241
- $msg = $e->getMessage();
242
-
243
- echo '<b>' . $msg . '</b>';
244
- exit;
245
- }
246
-
247
- if ($success) {
248
- //echo 'ok';
249
- } else {
250
- echo '<b>Conversion failed. None of the tried converters are operational</b>';
251
- }
252
- echo '</body></html>';
253
- }
254
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/ConverterHelper.php DELETED
@@ -1,260 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters;
4
-
5
- //use WebPConvert\Converters\Cwebp;
6
-
7
- use WebPConvert\Exceptions\ConverterNotFoundException;
8
- use WebPConvert\Exceptions\CreateDestinationFileException;
9
- use WebPConvert\Exceptions\CreateDestinationFolderException;
10
- use WebPConvert\Exceptions\InvalidFileExtensionException;
11
- use WebPConvert\Exceptions\TargetNotFoundException;
12
-
13
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
14
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
15
-
16
- class ConverterHelper
17
- {
18
- public static $allowedExtensions = ['jpg', 'jpeg', 'png'];
19
-
20
- public static $defaultOptions = [
21
- 'quality' => 'auto',
22
- 'max-quality' => 85,
23
- 'default-quality' => 80,
24
- 'metadata' => 'none',
25
- 'method' => 6,
26
- 'low-memory' => false,
27
- 'lossless' => false,
28
- 'converters' => ['cwebp', 'gd', 'imagick']
29
- ];
30
-
31
- public static function mergeOptions($options, $extraOptions)
32
- {
33
- return $options;
34
- }
35
-
36
- public static function getClassNameOfConverter($converterId)
37
- {
38
- return 'WebPConvert\\Converters\\' . ucfirst($converterId);
39
- }
40
-
41
-
42
- /* Call the "convert" method on a converter, by id.
43
- - but also prepares options (merges in the $extraOptions of the converter),
44
- prepares destination folder, and runs some standard validations */
45
- public static function runConverter($converterId, $source, $destination, $options = [], $prepareDestinationFolder = true, $logger = null)
46
- {
47
- if ($prepareDestinationFolder) {
48
- ConverterHelper::prepareDestinationFolderAndRunCommonValidations($source, $destination);
49
- }
50
-
51
- if (!isset($logger)) {
52
- $logger = new \WebPConvert\Loggers\VoidLogger();
53
- }
54
-
55
- $className = self::getClassNameOfConverter($converterId);
56
- if (!is_callable([$className, 'convert'])) {
57
- throw new ConverterNotFoundException();
58
- }
59
-
60
- // Prepare options.
61
- // - Remove 'converters'
62
- $defaultOptions = self::$defaultOptions;
63
- unset($defaultOptions['converters']);
64
-
65
- // - Merge defaults of the converters extra options into the standard default options.
66
- $defaultOptions = array_merge($defaultOptions, array_column($className::$extraOptions, 'default', 'name'));
67
-
68
- // - Merge $defaultOptions into provided options
69
- $options = array_merge($defaultOptions, $options);
70
-
71
- // Individual converters do not accept quality = auto. They need a number.
72
- // Change $options['quality'] to number, based on quality of source and several settings
73
-
74
- self::processQualityOption($source, $options, $logger);
75
-
76
- call_user_func(
77
- [$className, 'doConvert'],
78
- $source,
79
- $destination,
80
- $options,
81
- $logger
82
- );
83
-
84
- if (!file_exists($destination)) {
85
- throw new ConverterFailedException('Destination file is not there');
86
- }
87
- }
88
-
89
- /* Try to detect quality of jpeg.
90
- If not possible, nothing is returned (null). Otherwise quality is returned (int)
91
- */
92
- public static function detectQualityOfJpg($filename)
93
- {
94
- // Try Imagick extension
95
- if (extension_loaded('imagick') && class_exists('Imagick')) {
96
- $img = new Imagick($filename);
97
-
98
- // The required function is available as from PECL imagick v2.2.2
99
- if (method_exists($img, 'getImageCompressionQuality')) {
100
- return $img->getImageCompressionQuality();
101
- }
102
- }
103
-
104
- if (function_exists('shell_exec')) {
105
-
106
- // Try Imagick
107
- $quality = shell_exec("identify -format '%Q' " . $filename);
108
- if ($quality) {
109
- return intval($quality);
110
- }
111
-
112
- // Try GraphicsMagick
113
- $quality = shell_exec("gm identify -format '%Q' " . $filename);
114
- if ($quality) {
115
- return intval($quality);
116
- }
117
- }
118
- }
119
-
120
- public static function processQualityOption($source, &$options, $logger)
121
- {
122
- if (isset($options['_calculated_quality'])) {
123
- return;
124
- }
125
- if ($options['quality'] == 'auto') {
126
- $q = self::detectQualityOfJpg($source);
127
- //$logger->log('Quality set to auto... Quality of source: ');
128
- if (!$q) {
129
- $q = $options['default-quality'];
130
- $logger->logLn('Quality of source could not be established (Imagick or GraphicsMagick is required) - Using default instead (' . $options['default-quality'] . ').');
131
-
132
- // this allows the wpc converter to know
133
- $options['_quality_could_not_be_detected'] = true;
134
- } else {
135
- if ($q > $options['max-quality']) {
136
- $logger->log('Quality of source is ' . $q . '. This is higher than max-quality, so using that instead (' . $options['max-quality'] . ')');
137
- } else {
138
- $logger->log('Quality set to same as source: ' . $q);
139
- }
140
- }
141
- $logger->ln();
142
- $q = min($q, $options['max-quality']);
143
-
144
- $options['_calculated_quality'] = $q;
145
- //$logger->logLn('Using quality: ' . $options['quality']);
146
- } else {
147
- $logger->logLn('Quality: ' . $options['quality'] . '. Consider setting quality to "auto" instead. It is generally a better idea');
148
- $options['_calculated_quality'] = $options['quality'];
149
- }
150
- $logger->ln();
151
- }
152
-
153
-
154
- public static function getExtension($filePath)
155
- {
156
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
157
- return strtolower($fileExtension);
158
- }
159
-
160
- // Throws an exception if the provided file doesn't exist
161
- public static function isValidTarget($filePath)
162
- {
163
- if (!file_exists($filePath)) {
164
- throw new TargetNotFoundException('File or directory not found: ' . $filePath);
165
- }
166
-
167
- return true;
168
- }
169
-
170
- // Throws an exception if the provided file's extension is invalid
171
- public static function isAllowedExtension($filePath)
172
- {
173
- $fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
174
- if (!in_array(strtolower($fileExtension), self::$allowedExtensions)) {
175
- throw new InvalidFileExtensionException('Unsupported file extension: ' . $fileExtension);
176
- }
177
-
178
- return true;
179
- }
180
-
181
- // Creates folder in provided path & sets correct permissions
182
- public static function createWritableFolder($filePath)
183
- {
184
- $folder = pathinfo($filePath, PATHINFO_DIRNAME);
185
- if (!file_exists($folder)) {
186
- // TODO: what if this is outside open basedir?
187
- // see http://php.net/manual/en/ini.core.php#ini.open-basedir
188
-
189
- // First, we have to figure out which permissions to set.
190
- // We want same permissions as parent folder
191
- // But which parent? - the parent to the first missing folder
192
-
193
- $parentFolders = explode('/', $folder);
194
- $poppedFolders = [];
195
-
196
- while (!(file_exists(implode('/', $parentFolders))) && count($parentFolders) > 0) {
197
- array_unshift($poppedFolders, array_pop($parentFolders));
198
- }
199
-
200
- // Retrieving permissions of closest existing folder
201
- $closestExistingFolder = implode('/', $parentFolders);
202
- $permissions = fileperms($closestExistingFolder) & 000777;
203
-
204
- // Trying to create the given folder
205
- // Notice: mkdir emits a warning on failure. It would be nice to suppress that, if possible
206
- if (!mkdir($folder, $permissions, true)) {
207
- throw new CreateDestinationFolderException('Failed creating folder: ' . $folder);
208
- }
209
-
210
-
211
- // `mkdir` doesn't respect permissions, so we have to `chmod` each created subfolder
212
- foreach ($poppedFolders as $subfolder) {
213
- $closestExistingFolder .= '/' . $subfolder;
214
- // Setting directory permissions
215
- chmod($folder, $permissions);
216
- }
217
- }
218
-
219
- // Checks if there's a file in $filePath & if writing permissions are correct
220
- if (file_exists($filePath) && !is_writable($filePath)) {
221
- throw new CreateDestinationFileException('Cannot overwrite ' . basename($filePath) . ' - check file permissions.');
222
- }
223
-
224
- // There's either a rewritable file in $filePath or none at all.
225
- // If there is, simply attempt to delete it
226
- if (file_exists($filePath) && !unlink($filePath)) {
227
- throw new CreateDestinationFileException('Existing file cannot be removed: ' . basename($filePath));
228
- }
229
-
230
- return true;
231
- }
232
-
233
- public static function prepareDestinationFolderAndRunCommonValidations($source, $destination)
234
- {
235
- self::isValidTarget($source);
236
- self::isAllowedExtension($source);
237
- self::createWritableFolder($destination);
238
- }
239
-
240
- public static function initCurlForConverter()
241
- {
242
- if (!extension_loaded('curl')) {
243
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
244
- }
245
-
246
- if (!function_exists('curl_init')) {
247
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
248
- }
249
-
250
- if (!function_exists('curl_file_create')) {
251
- throw new ConverterNotOperationalException('Required curl_file_create() function is not available (requires PHP > 5.5).');
252
- }
253
-
254
- $ch = curl_init();
255
- if (!$ch) {
256
- throw new ConverterNotOperationalException('Could not initialise cURL.');
257
- }
258
- return $ch;
259
- }
260
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Cwebp.php DELETED
@@ -1,259 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters;
4
-
5
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
6
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
7
-
8
- class Cwebp
9
- {
10
- public static $extraOptions = [
11
- [
12
- 'name' => 'use-nice',
13
- 'type' => 'boolean',
14
- 'sensitive' => false,
15
- 'default' => false,
16
- 'required' => false
17
- ],
18
- ];
19
-
20
- public static function convert($source, $destination, $options = [])
21
- {
22
- ConverterHelper::runConverter('cwebp', $source, $destination, $options, true);
23
- }
24
-
25
- // System paths to look for cwebp binary
26
- private static $cwebpDefaultPaths = [
27
- '/usr/bin/cwebp',
28
- '/usr/local/bin/cwebp',
29
- '/usr/gnu/bin/cwebp',
30
- '/usr/syno/bin/cwebp'
31
- ];
32
-
33
- // OS-specific binaries included in this library, along with hashes
34
- private static $suppliedBinariesInfo = [
35
- 'WinNT' => [ 'cwebp.exe', '49e9cb98db30bfa27936933e6fd94d407e0386802cb192800d9fd824f6476873'],
36
- 'Darwin' => [ 'cwebp-mac12', 'a06a3ee436e375c89dbc1b0b2e8bd7729a55139ae072ed3f7bd2e07de0ebb379'],
37
- 'SunOS' => [ 'cwebp-sol', '1febaffbb18e52dc2c524cda9eefd00c6db95bc388732868999c0f48deb73b4f'],
38
- 'FreeBSD' => [ 'cwebp-fbsd', 'e5cbea11c97fadffe221fdf57c093c19af2737e4bbd2cb3cd5e908de64286573'],
39
- 'Linux' => [ 'cwebp-linux', '916623e5e9183237c851374d969aebdb96e0edc0692ab7937b95ea67dc3b2568']
40
- ];
41
-
42
- private static function escapeFilename($string)
43
- {
44
- // Escaping whitespace
45
- $string = preg_replace('/\s/', '\\ ', $string);
46
-
47
- // filter_var() is should normally be available, but it is not always
48
- // - https://stackoverflow.com/questions/11735538/call-to-undefined-function-filter-var
49
- if (function_exists('filter_var')) {
50
- // Sanitize quotes
51
- $string = filter_var($string, FILTER_SANITIZE_MAGIC_QUOTES);
52
-
53
- // Stripping control characters
54
- // see https://stackoverflow.com/questions/12769462/filter-flag-strip-low-vs-filter-flag-strip-high
55
- $string = filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
56
- }
57
-
58
- return $string;
59
- }
60
-
61
- // Checks if 'Nice' is available
62
- private static function hasNiceSupport()
63
- {
64
- exec("nice 2>&1", $niceOutput);
65
-
66
- if (is_array($niceOutput) && isset($niceOutput[0])) {
67
- if (preg_match('/usage/', $niceOutput[0]) || (preg_match('/^\d+$/', $niceOutput[0]))) {
68
- /*
69
- * Nice is available - default niceness (+10)
70
- * https://www.lifewire.com/uses-of-commands-nice-renice-2201087
71
- * https://www.computerhope.com/unix/unice.htm
72
- */
73
-
74
- return true;
75
- }
76
-
77
- return false;
78
- }
79
- }
80
-
81
- //
82
- private static function executeBinary($binary, $commandOptions, $useNice, $logger)
83
- {
84
- $command = ($useNice ? 'nice ' : '') . $binary . ' ' . $commandOptions;
85
-
86
- $logger->logLn('Trying to execute binary:' . $binary);
87
- //$logger->logLn();
88
-
89
- exec($command, $output, $returnCode);
90
-
91
- switch ($returnCode) {
92
- case 0:
93
- $logger->logLn('Success!');
94
- break;
95
- case 126:
96
- $logger->logLn('Permission denied. The user that the command was run with (' . shell_exec('whoami') . ') does not have permission to execute that binary.');
97
- break;
98
- case 127:
99
- $logger->logLn('No binary found at that location');
100
- break;
101
- default:
102
- $logger->logLn('Failed. Return code:' . $returnCode . '. See http://tldp.org/LDP/abs/html/exitcodes.html for failcodes');
103
- }
104
- return $returnCode;
105
- }
106
-
107
- // Although this method is public, do not call directly.
108
- public static function doConvert($source, $destination, $options = [], $logger)
109
- {
110
- $errorMsg = '';
111
- // Force lossless option to true for PNG images
112
- if (ConverterHelper::getExtension($source) == 'png') {
113
- $options['lossless'] = true;
114
- }
115
-
116
- if (!function_exists('exec')) {
117
- throw new ConverterNotOperationalException('exec() is not enabled.');
118
- }
119
-
120
- /*
121
- * Prepare cwebp options
122
- */
123
-
124
- // Metadata (all, exif, icc, xmp or none (default))
125
- // Comma-separated list of existing metadata to copy from input to output
126
- $metadata = '-metadata ' . $options['metadata'];
127
-
128
- // Image quality
129
- $quality = '-q ' . $options['_calculated_quality'];
130
-
131
- // Losless PNG conversion
132
- $lossless = ($options['lossless'] ? '-lossless' : '');
133
-
134
- // Built-in method option
135
- $method = ' -m ' . strval($options['method']);
136
-
137
-
138
- // TODO:
139
- // Why not use -af ? (https://developers.google.com/speed/webp/docs/cwebp)
140
- // Would it be possible get a quality similar to source?
141
- // It seems so: "identify -format '%Q' yourimage.jpg" (https://stackoverflow.com/questions/2024947/is-it-possible-to-tell-the-quality-level-of-a-jpeg)
142
- // -- With -jpeg_like option, or perhaps the -size option
143
-
144
- // Built-in low memory option
145
- $lowMemory = '';
146
- if ($options['low-memory']) {
147
- $lowMemory = '-low_memory';
148
- }
149
-
150
- $commandOptionsArray = [
151
- $metadata = $metadata,
152
- $quality = $quality,
153
- $lossless = $lossless,
154
- $method = $method,
155
- $lowMemory = $lowMemory,
156
- $input = self::escapeFilename($source),
157
- $output = '-o ' . self::escapeFilename($destination),
158
- $stderrRedirect = '2>&1'
159
- ];
160
-
161
- $useNice = (($options['use-nice']) && self::hasNiceSupport()) ? true : false;
162
-
163
- $commandOptions = implode(' ', $commandOptionsArray);
164
-
165
-
166
- // Init with common system paths
167
- $cwebpPathsToTest = self::$cwebpDefaultPaths;
168
-
169
- // Remove paths that doesn't exist
170
- $cwebpPathsToTest = array_filter($cwebpPathsToTest, function ($binary) {
171
- //return file_exists($binary);
172
- return @is_readable($binary);
173
- });
174
-
175
- // Try all common paths that exitst
176
- $success = false;
177
- foreach ($cwebpPathsToTest as $index => $binary) {
178
- $success = (self::executeBinary($binary, $commandOptions, $useNice, $logger) == 0);
179
- if ($success) {
180
- break;
181
- }
182
- }
183
- if (!$success) {
184
- //$logger->logLn('');
185
- if (count($cwebpPathsToTest) > 0) {
186
- $errorMsg .= 'Found cwebp binaries at these locations: "' . implode('", "', $cwebpPathsToTest) . '". However, executing these failed. ';
187
- } else {
188
- $errorMsg .= 'Found no cwebp binaries in any common locations. ';
189
- }
190
- }
191
-
192
- if (!$success) {
193
-
194
- // Try supplied binary (if available for OS, and hash is correct)
195
- if (isset(self::$suppliedBinariesInfo[PHP_OS])) {
196
- $info = self::$suppliedBinariesInfo[PHP_OS];
197
-
198
- $file = $info[0];
199
- $hash = $info[1];
200
-
201
- $binaryFile = __DIR__ . '/Binaries/' . $file;
202
-
203
- // The file should exist, but may have been removed manually.
204
- if (file_exists($binaryFile)) {
205
- // File exists, now generate its hash
206
-
207
- // hash_file() is normally available, but it is not always
208
- // - https://stackoverflow.com/questions/17382712/php-5-3-20-undefined-function-hash
209
- // If available, validate that hash is correct.
210
- $proceedAfterHashCheck = true;
211
- if (function_exists('hash_file')) {
212
- $binaryHash = hash_file('sha256', $binaryFile);
213
-
214
- if ($binaryHash != $hash) {
215
- $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 . '. ';
216
- $proceedAfterHashCheck = false;
217
- }
218
- }
219
- if ($proceedAfterHashCheck) {
220
- $returnCode = self::executeBinary($binaryFile, $commandOptions, $useNice, $logger);
221
- if ($returnCode == 0) {
222
- $success = true;
223
- } else {
224
- $errorMsg .= 'Tried executing supplied binary (' . $binaryFile . '), but that failed too: ';
225
- switch ($returnCode) {
226
- case 126:
227
- $errorMsg .= 'Permission denied (user "' . trim(shell_exec('whoami')) . '" does not have permission to execute the binary)';
228
- break;
229
- default:
230
- $errorMsg .= 'Fail code: ' . $returnCode;
231
- }
232
- }
233
- }
234
- } else {
235
- $errorMsg .= 'Supplied binary not found:' . $binaryFile;
236
- }
237
- } else {
238
- $errorMsg .= 'No supplied binaries found for OS:' . PHP_OS;
239
- }
240
- }
241
-
242
-
243
-
244
- // cwebp sets file permissions to 664 but instead ..
245
- // .. $destination's parent folder's permissions should be used (except executable bits)
246
- if ($success) {
247
- $destinationParent = dirname($destination);
248
- $fileStatistics = stat($destinationParent);
249
-
250
- // Apply same permissions as parent folder but strip off the executable bits
251
- $permissions = $fileStatistics['mode'] & 0000666;
252
- chmod($destination, $permissions);
253
- }
254
-
255
- if (!$success) {
256
- throw new ConverterNotOperationalException($errorMsg);
257
- }
258
- }
259
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Ewww.php DELETED
@@ -1,197 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters;
4
-
5
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
6
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
7
-
8
- class Ewww
9
- {
10
- public static $extraOptions = [
11
- [
12
- 'name' => 'key',
13
- 'type' => 'string',
14
- 'sensitive' => true,
15
- 'default' => '',
16
- 'required' => true
17
- ],
18
- ];
19
-
20
- public static function convert($source, $destination, $options = [])
21
- {
22
- ConverterHelper::runConverter('ewww', $source, $destination, $options, true);
23
- }
24
-
25
- // Although this method is public, do not call directly.
26
- public static function doConvert($source, $destination, $options = [], $logger)
27
- {
28
- if ($options['key'] == '') {
29
- throw new ConverterNotOperationalException('Missing API key.');
30
- }
31
- if (strlen($options['key']) < 20) {
32
- throw new ConverterNotOperationalException('Key is invalid. Keys are supposed to be 32 characters long - your key is much shorter');
33
- }
34
-
35
- $keyStatus = self::getKeyStatus($options['key']);
36
- switch ($keyStatus) {
37
- case 'great':
38
- break;
39
- case 'exceeded':
40
- throw new ConverterNotOperationalException('quota has exceeded');
41
- break;
42
- case 'invalid':
43
- throw new ConverterNotOperationalException('key is invalid');
44
- break;
45
- }
46
-
47
- $ch = ConverterHelper::initCurlForConverter();
48
-
49
- $curlOptions = [
50
- 'api_key' => $options['key'],
51
- 'webp' => '1',
52
- 'file' => curl_file_create($source),
53
- 'domain' => $_SERVER['HTTP_HOST'],
54
- 'quality' => $options['_calculated_quality'],
55
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
56
- ];
57
-
58
- curl_setopt_array($ch, [
59
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
60
- CURLOPT_HTTPHEADER => [
61
- 'User-Agent: WebPConvert',
62
- 'Accept: image/*'
63
- ],
64
- CURLOPT_POSTFIELDS => $curlOptions,
65
- CURLOPT_BINARYTRANSFER => true,
66
- CURLOPT_RETURNTRANSFER => true,
67
- CURLOPT_HEADER => false,
68
- CURLOPT_SSL_VERIFYPEER => false
69
- ]);
70
-
71
- $response = curl_exec($ch);
72
-
73
- if (curl_errno($ch)) {
74
- throw new ConverterNotOperationalException(curl_error($ch));
75
- }
76
-
77
- // The API does not always return images.
78
- // For example, it may return a message such as '{"error":"invalid","t":"exceeded"}
79
- // Messages has a http content type of ie 'text/html; charset=UTF-8
80
- // Images has application/octet-stream.
81
- // So verify that we got an image back.
82
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
83
-
84
- //echo curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
85
- curl_close($ch);
86
-
87
- /* May return this: {"error":"invalid","t":"exceeded"} */
88
- $responseObj = json_decode($response);
89
- if (isset($responseObj->error)) {
90
- //echo 'error:' . $responseObj->error . '<br>';
91
- //echo $response;
92
- //self::blacklistKey($key);
93
- //throw new ConverterNotOperationalException('The key is invalid. Blacklisted it!');
94
- throw new ConverterNotOperationalException('The key is invalid');
95
- }
96
-
97
- throw new ConverterNotOperationalException('ewww api did not return an image. It could be that the key is invalid. Response: ' . $response);
98
- }
99
-
100
- // Not sure this can happen. So just in case
101
- if ($response == '') {
102
- throw new ConverterNotOperationalException('ewww api did not return anything');
103
- }
104
-
105
- $success = file_put_contents($destination, $response);
106
-
107
- if (!$success) {
108
- throw new ConverterFailedException('Error saving file');
109
- }
110
- }
111
-
112
- /*
113
- public static function blacklistKey($key)
114
- {
115
- }
116
-
117
- public static function isKeyBlacklisted($key)
118
- {
119
- }*/
120
-
121
- /**
122
- * Return "great", "exceeded" or "invalid"
123
- */
124
- public static function getKeyStatus($key)
125
- {
126
- $ch = ConverterHelper::initCurlForConverter();
127
-
128
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/verify/");
129
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
130
- curl_setopt($ch, CURLOPT_POSTFIELDS, [
131
- 'api_key' => $key
132
- ]);
133
-
134
- // The 403 forbidden is avoided with this line.
135
- 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)');
136
-
137
- $response = curl_exec($ch);
138
- // echo $response;
139
- if (curl_errno($ch)) {
140
- throw new \Exception(curl_error($ch));
141
- }
142
- curl_close($ch);
143
-
144
- // Possible responses:
145
- // “great” = verification successful
146
- // “exceeded” = indicates a valid key with no remaining image credits.
147
- // an empty response indicates that the key is not valid
148
-
149
- if ($response == '') {
150
- return 'invalid';
151
- }
152
- $responseObj = json_decode($response);
153
- if (isset($responseObj->error)) {
154
- if ($responseObj->error == 'invalid') {
155
- return 'invalid';
156
- } else {
157
- throw new \Exception('Ewww returned unexpected error: ' . $response);
158
- }
159
- }
160
- if (!isset($responseObj->status)) {
161
- throw new \Exception('Ewww returned unexpected response to verify request: ' . $response);
162
- }
163
- switch ($responseObj->status) {
164
- case 'great':
165
- case 'exceeded':
166
- return $responseObj->status;
167
- }
168
- throw new \Exception('Ewww returned unexpected status to verify request: "' . $responseObj->status . '"');
169
- }
170
-
171
- public static function isWorkingKey($key)
172
- {
173
- return (self::getKeyStatus($key) == 'great');
174
- }
175
-
176
- public static function isValidKey($key)
177
- {
178
- return (self::getKeyStatus($key) != 'invalid');
179
- }
180
-
181
- public static function getQuota($key)
182
- {
183
- $ch = ConverterHelper::initCurlForConverter();
184
-
185
- curl_setopt($ch, CURLOPT_URL, "https://optimize.exactlywww.com/quota/");
186
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
187
- curl_setopt($ch, CURLOPT_POSTFIELDS, [
188
- 'api_key' => $key
189
- ]);
190
- 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)');
191
-
192
- $response = curl_exec($ch);
193
- return $response; // ie -830 23. Seems to return empty for invalid keys
194
- // or empty
195
- //echo $response;
196
- }
197
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Exceptions/ConversionDeclinedException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class ConversionDeclinedException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter declined converting';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Exceptions/ConverterFailedException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class ConverterFailedException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter failed converting, although requirements seemed to be met';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Exceptions/ConverterNotOperationalException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class ConverterNotOperationalException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter is not operational';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Gd.php DELETED
@@ -1,83 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters;
4
-
5
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
6
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
7
- use WebPConvert\Converters\Exceptions\ConversionDeclinedException;
8
-
9
- use WebPConvert\Converters\ConverterHelper;
10
-
11
- class Gd
12
- {
13
- public static $extraOptions = [
14
- [
15
- 'name' => 'skip-pngs',
16
- 'type' => 'boolean',
17
- 'sensitive' => false,
18
- 'default' => true,
19
- 'required' => false
20
- ],
21
- ];
22
-
23
- public static function convert($source, $destination, $options = [])
24
- {
25
- ConverterHelper::runConverter('gd', $source, $destination, $options, true);
26
- }
27
-
28
- // Although this method is public, do not call directly.
29
- public static function doConvert($source, $destination, $options = [], $logger)
30
- {
31
- if (!extension_loaded('gd')) {
32
- throw new ConverterNotOperationalException('Required GD extension is not available.');
33
- }
34
-
35
- if (!function_exists('imagewebp')) {
36
- throw new ConverterNotOperationalException('Required imagewebp() function is not available.');
37
- }
38
-
39
- switch (ConverterHelper::getExtension($source)) {
40
- case 'png':
41
- if (!$options['skip-pngs']) {
42
- if (!function_exists('imagecreatefrompng')) {
43
- throw new ConverterNotOperationalException('Required imagecreatefrompng() function is not available.');
44
- }
45
- $image = imagecreatefrompng($source);
46
- if (!$image) {
47
- throw new ConverterFailedException('imagecreatefrompng("' . $source . '") failed');
48
- }
49
- } else {
50
- throw new ConversionDeclinedException('PNG file skipped. GD is configured not to convert PNGs');
51
- }
52
- break;
53
- default:
54
- if (!function_exists('imagecreatefromjpeg')) {
55
- throw new ConverterNotOperationalException('Required imagecreatefromjpeg() function is not available.');
56
- }
57
- $image = imagecreatefromjpeg($source);
58
- if (!$image) {
59
- throw new ConverterFailedException('imagecreatefromjpeg("' . $source . '") failed');
60
- }
61
- }
62
-
63
- // Checks if either imagecreatefromjpeg() or imagecreatefrompng() returned false
64
-
65
- $success = imagewebp($image, $destination, $options['_calculated_quality']);
66
-
67
- if (!$success) {
68
- throw new ConverterFailedException('Call to imagewebp() failed. Probably failed writing file');
69
- }
70
-
71
- /*
72
- * This hack solves an `imagewebp` bug
73
- * See https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
74
- *
75
- */
76
-
77
- if (filesize($destination) % 2 == 1) {
78
- file_put_contents($destination, "\0", FILE_APPEND);
79
- }
80
-
81
- imagedestroy($image);
82
- }
83
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Imagick.php DELETED
@@ -1,76 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters;
4
-
5
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
6
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
7
-
8
- //use WebPConvert\Exceptions\TargetNotFoundException;
9
-
10
- class Imagick
11
- {
12
- public static $extraOptions = [];
13
-
14
- public static function convert($source, $destination, $options = [])
15
- {
16
- ConverterHelper::runConverter('imagick', $source, $destination, $options, true);
17
- }
18
-
19
- // Although this method is public, do not call directly.
20
- public static function doConvert($source, $destination, $options = [], $logger)
21
- {
22
- if (!extension_loaded('imagick')) {
23
- throw new ConverterNotOperationalException('Required iMagick extension is not available.');
24
- }
25
-
26
- if (!class_exists('Imagick')) {
27
- throw new ConverterNotOperationalException('iMagick is installed, but not correctly. The class Imagick is not available');
28
- }
29
-
30
- $im = new \Imagick($source);
31
-
32
- // Throws an exception if iMagick does not support WebP conversion
33
- if (!in_array('WEBP', $im->queryFormats())) {
34
- throw new ConverterNotOperationalException('iMagick was compiled without WebP support.');
35
- }
36
-
37
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
38
-
39
- // Force lossless option to true for PNG images
40
- if (ConverterHelper::getExtension($source) == 'png') {
41
- $options['lossless'] = true;
42
- }
43
-
44
- $im->setImageFormat('WEBP');
45
-
46
- /*
47
- * More about iMagick's WebP options:
48
- * http://www.imagemagick.org/script/webp.php
49
- * https://developers.google.com/speed/webp/docs/cwebp
50
- * https://stackoverflow.com/questions/37711492/imagemagick-specific-webp-calls-in-php
51
- */
52
-
53
- // TODO: We could easily support all webp options with a loop
54
- $im->setOption('webp:method', strval($options['method']));
55
- $im->setOption('webp:low-memory', strval($options['low-memory']));
56
- $im->setOption('webp:lossless', strval($options['lossless']));
57
-
58
-
59
-
60
- $im->setImageCompressionQuality($options['_calculated_quality']);
61
-
62
- // TODO:
63
- // Should we set alpha channel for PNG's like suggested here:
64
- // https://gauntface.com/blog/2014/09/02/webp-support-with-imagemagick-and-php ??
65
- // It seems that alpha channel works without... (at least I see completely transparerent pixels)
66
-
67
- // TODO: Check out other iMagick methods, see http://php.net/manual/de/imagick.writeimage.php#114714
68
- // 1. file_put_contents($destination, $im)
69
- // 2. $im->writeImage($destination)
70
- $success = $im->writeImageFile(fopen($destination, 'wb'));
71
-
72
- if (!$success) {
73
- throw new ConverterFailedException('Failed writing file');
74
- }
75
- }
76
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Converters/Wpc.php DELETED
@@ -1,171 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Converters;
4
-
5
- use WebPConvert\Converters\Exceptions\ConverterNotOperationalException;
6
- use WebPConvert\Converters\Exceptions\ConverterFailedException;
7
-
8
- class Wpc
9
- {
10
- public static $extraOptions = [
11
- [
12
- 'name' => 'secret',
13
- 'type' => 'string',
14
- 'sensitive' => true,
15
- 'default' => 'my dog is white',
16
- 'required' => true
17
- ],
18
- [
19
- 'name' => 'url',
20
- 'type' => 'string',
21
- 'sensitive' => true,
22
- 'default' => '',
23
- 'required' => true
24
- ],
25
- ];
26
-
27
- public static function convert($source, $destination, $options = [])
28
- {
29
- ConverterHelper::runConverter('wpc', $source, $destination, $options, true);
30
- }
31
-
32
- // Although this method is public, do not call directly.
33
- public static function doConvert($source, $destination, $options = [], $logger)
34
- {
35
- if ($options['url'] == '') {
36
- throw new ConverterNotOperationalException('Missing URL. You must install WebpConvertCloudService on a server, and supply url');
37
- }
38
-
39
- if (!extension_loaded('curl')) {
40
- throw new ConverterNotOperationalException('Required cURL extension is not available.');
41
- }
42
-
43
- if (!function_exists('curl_init')) {
44
- throw new ConverterNotOperationalException('Required url_init() function is not available.');
45
- }
46
-
47
-
48
- if (!function_exists('curl_file_create')) {
49
- throw new ConverterNotOperationalException('Required curl_file_create() PHP function is not available (requires PHP > 5.5).');
50
- }
51
-
52
- if (!empty($options['secret'])) {
53
- // if secret is set, we need md5() and md5_file() functions
54
- if (!function_exists('md5')) {
55
- throw new ConverterNotOperationalException('A secret has been set, which requires us to create a md5 hash from the secret and the file contents. But the required md5() PHP function is not available.');
56
- }
57
- if (!function_exists('md5_file')) {
58
- throw new ConverterNotOperationalException('A secret has been set, which requires us to create a md5 hash from the secret and the file contents. But the required md5_file() PHP function is not available.');
59
- }
60
- }
61
-
62
- // Got some code here:
63
- // https://coderwall.com/p/v4ps1a/send-a-file-via-post-with-curl-and-php
64
-
65
- $ch = curl_init();
66
- if (!$ch) {
67
- throw new ConverterNotOperationalException('Could not initialise cURL.');
68
- }
69
-
70
- $optionsToSend = $options;
71
-
72
- if (isset($options['_quality_could_not_be_detected'])) {
73
- // quality was set to "auto", but we could not meassure the quality of the jpeg locally
74
- // Ask the cloud service to do it, rather than using what we came up with.
75
- $optionsToSend['quality'] = 'auto';
76
- } else {
77
- $optionsToSend['quality'] = $options['_calculated_quality'];
78
- }
79
-
80
- unset($optionsToSend['converters']);
81
- unset($optionsToSend['secret']);
82
- unset($optionsToSend['_quality_could_not_be_detected']);
83
- unset($optionsToSend['_calculated_quality']);
84
-
85
- curl_setopt_array($ch, [
86
- CURLOPT_URL => $options['url'],
87
- CURLOPT_POST => 1,
88
- CURLOPT_POSTFIELDS => [
89
- 'file' => curl_file_create($source),
90
- 'hash' => md5(md5_file($source) . $options['secret']),
91
- 'options' => json_encode($optionsToSend)
92
- ],
93
- CURLOPT_BINARYTRANSFER => true,
94
- CURLOPT_RETURNTRANSFER => true,
95
- CURLOPT_HEADER => false,
96
- CURLOPT_SSL_VERIFYPEER => false
97
- ]);
98
-
99
- $response = curl_exec($ch);
100
- if (curl_errno($ch)) {
101
- throw new ConverterNotOperationalException('Curl error:' . curl_error($ch));
102
- }
103
-
104
- // Check if we got a 404
105
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
106
- if ($httpCode == 404) {
107
- curl_close($ch);
108
- throw new ConverterFailedException('WPC was not found and the specified URL - we got a 404 response.');
109
- }
110
-
111
- // The WPC cloud service either returns an image or an error message
112
- // Images has application/octet-stream.
113
- // Verify that we got an image back.
114
- if (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) != 'application/octet-stream') {
115
- curl_close($ch);
116
-
117
- if (substr($response, 0, 1) == '{') {
118
- $responseObj = json_decode($response, true);
119
- if (isset($responseObj['errorCode'])) {
120
- switch ($responseObj['errorCode']) {
121
- case 0:
122
- throw new ConverterFailedException('WPC reported problems with server setup: "' . $responseObj['errorMessage'] . '"');
123
- case 1:
124
- throw new ConverterFailedException('WPC denied us access to the service: "' . $responseObj['errorMessage'] . '"');
125
- default:
126
- throw new ConverterFailedException('WPC failed: "' . $responseObj['errorMessage'] . '"');
127
- }
128
- }
129
- }
130
-
131
- // WPC 0.1 returns 'failed![error messag]' when conversion fails. Handle that.
132
- if (substr($response, 0, 7) == 'failed!') {
133
- throw new ConverterFailedException('WPC failed converting image: "' . substr($response, 7) . '"');
134
- }
135
-
136
- $errorMsg = 'Error: Unexpected result. We did not receive an image. We received: "';
137
- $errorMsg .= str_replace("\r", '', str_replace("\n", '', htmlentities(substr($response, 0, 400))));
138
- throw new ConverterFailedException($errorMsg . '..."');
139
- //throw new ConverterNotOperationalException($response);
140
- }
141
-
142
- $success = file_put_contents($destination, $response);
143
- curl_close($ch);
144
-
145
- if (!$success) {
146
- throw new ConverterFailedException('Error saving file');
147
- }
148
- /*
149
- $curlOptions = [
150
- 'api_key' => $options['key'],
151
- 'webp' => '1',
152
- 'file' => curl_file_create($source),
153
- 'domain' => $_SERVER['HTTP_HOST'],
154
- 'quality' => $options['quality'],
155
- 'metadata' => ($options['metadata'] == 'none' ? '0' : '1')
156
- ];
157
-
158
- curl_setopt_array($ch, [
159
- CURLOPT_URL => "https://optimize.exactlywww.com/v2/",
160
- CURLOPT_HTTPHEADER => [
161
- 'User-Agent: WebPConvert',
162
- 'Accept: image/*'
163
- ],
164
- CURLOPT_POSTFIELDS => $curlOptions,
165
- CURLOPT_BINARYTRANSFER => true,
166
- CURLOPT_RETURNTRANSFER => true,
167
- CURLOPT_HEADER => false,
168
- CURLOPT_SSL_VERIFYPEER => false
169
- ]);*/
170
- }
171
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Exceptions/ConverterNotFoundException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class ConverterNotFoundException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter does not exist.';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Exceptions/CreateDestinationFileException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class CreateDestinationFileException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter could not create destination file. Check file permisions!';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Exceptions/CreateDestinationFolderException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class CreateDestinationFolderException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter could not create destination folder. Check file permisions!';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Exceptions/InvalidFileExtensionException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class InvalidFileExtensionException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter does not accept the file extension';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Exceptions/TargetNotFoundException.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Exceptions;
4
-
5
- use WebPConvert\Exceptions\WebPConvertBaseException;
6
-
7
- class TargetNotFoundException extends WebPConvertBaseException
8
- {
9
- public $description = 'The converter could not locate source file';
10
- }
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Exceptions/WebPConvertBaseException.php DELETED
@@ -1,7 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Exceptions;
4
-
5
- class WebPConvertBaseException extends \Exception
6
- {
7
- }
 
 
 
 
 
 
 
vendor/webp-convert/Loggers/BaseLogger.php DELETED
@@ -1,26 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Loggers;
4
-
5
- abstract class BaseLogger
6
- {
7
- /*
8
- $msg: message to log
9
- $style: null | bold | italic
10
- */
11
- abstract public function log($msg, $style = '');
12
-
13
- abstract public function ln();
14
-
15
- public function logLn($msg, $style = '')
16
- {
17
- $this->log($msg, $style);
18
- $this->ln();
19
- }
20
-
21
- public function logLnLn($msg, $style = '')
22
- {
23
- $this->logLn($msg, $style);
24
- $this->ln();
25
- }
26
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Loggers/EchoLogger.php DELETED
@@ -1,22 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Loggers;
4
-
5
- class EchoLogger extends BaseLogger
6
- {
7
- public function log($msg, $style = '')
8
- {
9
- if ($style == 'bold') {
10
- echo '<b>' . $msg . '</b>';
11
- } elseif ($style == 'italic') {
12
- echo '<i>' . $msg . '</i>';
13
- } else {
14
- echo $msg;
15
- }
16
- }
17
-
18
- public function ln()
19
- {
20
- echo '<br>';
21
- }
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/Loggers/VoidLogger.php DELETED
@@ -1,14 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert\Loggers;
4
-
5
- class VoidLogger extends BaseLogger
6
- {
7
- public function log($msg, $style = '')
8
- {
9
- }
10
-
11
- public function ln()
12
- {
13
- }
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/WebPConvert.php DELETED
@@ -1,108 +0,0 @@
1
- <?php
2
-
3
- namespace WebPConvert;
4
-
5
- use WebPConvert\Converters\ConverterHelper;
6
-
7
- class WebPConvert
8
- {
9
-
10
- /*
11
- @param (string) $source: Absolute path to image to be converted (no backslashes). Image must be jpeg or png
12
- @param (string) $destination: Absolute path (no backslashes)
13
- @param (object) $options: Array of named options, such as 'quality' and 'metadata'
14
- */
15
- public static function convert($source, $destination, $options = [], $logger = null)
16
- {
17
- if (!isset($logger)) {
18
- $logger = new \WebPConvert\Loggers\VoidLogger();
19
- }
20
- ConverterHelper::prepareDestinationFolderAndRunCommonValidations($source, $destination);
21
-
22
- $options = array_merge(ConverterHelper::$defaultOptions, $options);
23
-
24
- ConverterHelper::processQualityOption($source, $options, $logger);
25
-
26
- // Force lossless option to true for PNG images
27
- if (ConverterHelper::getExtension($source) == 'png') {
28
- $options['lossless'] = true;
29
- }
30
-
31
- $defaultConverterOptions = $options;
32
- $defaultConverterOptions['converters'] = null;
33
-
34
- $firstFailException = null;
35
-
36
- foreach ($options['converters'] as $converter) {
37
- if (is_array($converter)) {
38
- $converterId = $converter['converter'];
39
- $converterOptions = $converter['options'];
40
- } else {
41
- $converterId = $converter;
42
- $converterOptions = [];
43
- }
44
-
45
- $converterOptions = array_merge($defaultConverterOptions, $converterOptions);
46
-
47
- try {
48
- $logger->logLn('Trying:' . $converterId, 'italic');
49
-
50
- // If quality is different, we must recalculate
51
- if ($converterOptions['quality'] != $defaultConverterOptions['quality']) {
52
- unset($converterOptions['_calculated_quality']);
53
- ConverterHelper::processQualityOption($source, $converterOptions, $logger);
54
- }
55
-
56
- ConverterHelper::runConverter($converterId, $source, $destination, $converterOptions, false, $logger);
57
-
58
- // Still here? - well, we did it! - job is done.
59
- $logger->logLn('ok', 'bold');
60
- return true;
61
- } catch (\WebPConvert\Converters\Exceptions\ConverterNotOperationalException $e) {
62
- // $logger->logLnLn($e->description . ' : ' . $e->getMessage());
63
- $logger->logLnLn($e->getMessage());
64
-
65
- // The converter is not operational.
66
- // Well, well, we will just have to try the next, then
67
- } catch (\WebPConvert\Converters\Exceptions\ConverterFailedException $e) {
68
- $logger->logLnLn($e->getMessage());
69
-
70
- // Converter failed in an anticipated, yet somewhat surprising fashion.
71
- // The converter seemed operational - requirements was in order - but it failed anyway.
72
- // This is moderately bad.
73
- // If some other converter can handle the conversion, we will let this one go.
74
- // But if not, we shall throw the exception
75
-
76
- if (!$firstFailException) {
77
- $firstFailException = $e;
78
- }
79
- } catch (\WebPConvert\Converters\Exceptions\ConversionDeclinedException $e) {
80
- $logger->logLnLn($e->getMessage());
81
-
82
- // The converter declined.
83
- // Gd is for example throwing this, when asked to convert a PNG, but configured not to
84
- // We also possibly rethrow this, because it may have come as a surprise to the user
85
- // who perhaps only tested jpg
86
- if (!$firstFailException) {
87
- $firstFailException = $e;
88
- }
89
- }
90
- }
91
-
92
- if ($firstFailException) {
93
- // At least one converter failed or declined.
94
- $logger->logLn('Conversion failed. None of the tried converters could convert the image', 'bold');
95
- } else {
96
- // All converters threw a ConverterNotOperationalException
97
- $logger->logLn('Conversion failed. None of the tried converters are operational', 'bold');
98
- }
99
-
100
- // No converters could do the job.
101
- // If one of them failed moderately bad, rethrow that exception.
102
- if ($firstFailException) {
103
- throw $firstFailException;
104
- }
105
-
106
- return false;
107
- }
108
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-convert/require-all.inc DELETED
@@ -1,20 +0,0 @@
1
- <?php
2
- require_once(__DIR__ . "/Exceptions/WebPConvertBaseException.php");
3
- require_once(__DIR__ . "/Loggers/BaseLogger.php");
4
- require_once(__DIR__ . "/WebPConvert.php");
5
- require_once(__DIR__ . "/Converters/ConverterHelper.php");
6
- require_once(__DIR__ . "/Converters/Cwebp.php");
7
- require_once(__DIR__ . "/Converters/Ewww.php");
8
- require_once(__DIR__ . "/Converters/Gd.php");
9
- require_once(__DIR__ . "/Converters/Imagick.php");
10
- require_once(__DIR__ . "/Converters/Wpc.php");
11
- require_once(__DIR__ . "/Exceptions/ConverterNotFoundException.php");
12
- require_once(__DIR__ . "/Exceptions/CreateDestinationFileException.php");
13
- require_once(__DIR__ . "/Exceptions/CreateDestinationFolderException.php");
14
- require_once(__DIR__ . "/Exceptions/InvalidFileExtensionException.php");
15
- require_once(__DIR__ . "/Exceptions/TargetNotFoundException.php");
16
- require_once(__DIR__ . "/Converters/Exceptions/ConversionDeclinedException.php");
17
- require_once(__DIR__ . "/Converters/Exceptions/ConverterFailedException.php");
18
- require_once(__DIR__ . "/Converters/Exceptions/ConverterNotOperationalException.php");
19
- require_once(__DIR__ . "/Loggers/EchoLogger.php");
20
- require_once(__DIR__ . "/Loggers/VoidLogger.php");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/webp-on-demand/WebPOnDemand.php DELETED
@@ -1,345 +0,0 @@
1
- <?php
2
- /*
3
- URL parameters:
4
-
5
- base-path:
6
- Sets the base path used for "source" and "destination-root" options.
7
- Must be relative to document root, or absolute (not recommended)
8
- When used in .htaccess, set it to the folder containing the .htaccess file, relative to document root.
9
- If for example document root is /var/www/example.com/ and you have a subdirectory "wordpress", which you
10
- want WebPOnDemand to work on, you should place .htaccess rules in the "wordpress" directory, and
11
- your "base-path" will be "wordpress"
12
- If not set, it defaults to be the path of webp-on-demand.php
13
-
14
- source: Path to source file.
15
- Path to source file, relative to 'base-path' option.
16
- The final path is calculated like this:
17
- [base-path] + [path to source file] + ".webp".
18
- absolute path is depreciated, but supported for backwards compatability.
19
-
20
- destination-root:
21
- The path of where you want the converted files to reside, relative to the 'base-path' option.
22
- If you want converted files to be put in the same folder as the originals, you can set destination-root to ".", or
23
- leave it blank. If you on the other hand want all converted files to reside in their own folder, set the
24
- destination-root to point to that folder. The converted files will be stored in a hierarchy that matches the source
25
- files. With destination-root set to "webp-cache", the source file "images/2017/cool.jpg" will be stored at
26
- "webp-cache/images/2017/cool.jpg.webp".
27
- Double-dots in paths are allowed, ie "../webp-cache"
28
- The final destination is calculated like this:
29
- [base-path] + [destination-root] + [path to source file] + ".webp".
30
- Default is "."
31
- You can also supply an absolute path
32
-
33
- quality (optional):
34
- The quality of the generated WebP image, "auto" or 0-100. Defaults to "auto"
35
-
36
- max-quality (optional):
37
- The maximum quality. Only relevant when quality is set to "auto"
38
-
39
- default-quality (optional):
40
- Fallback value for quality, if it isn't possible to detect quality of jpeg. Only relevant when quality is set to "auto"
41
-
42
- metadata (optional):
43
- If set to "none", all metadata will be stripped
44
- If set to "all", all metadata will be preserved
45
- Note however that not all converters supports preserving metadata. cwebp supports it, imagewebp does not.
46
-
47
- converters (optional):
48
- Comma-separated list of converters. Ie. "cwebp,gd".
49
- To pass options to the individual converters, see next.
50
- Also, check out the WebPConvert docs
51
-
52
- [converter-id]-[option-name] (optional):
53
- This pattern is used for setting options on the individual converters.
54
- Ie, in order to set the "key" option of the "ewww" converter, you pass "ewww-key".
55
-
56
- [converter-id]-[n]-[option-name] (optional):
57
- Use this pattern for targeting options of a converter, that are used multiple times. However, use the pattern above
58
- for targeting the first occurence. `n` stands for the nth occurence of that converter in the `converters` option.
59
- Example: `...&converters=cwebp,ewww,ewww,gd,ewww&ewww-key=xxx&ewww-2-key=yyy&ewww-3-key=zzz&gd-skip-pngs=1`
60
-
61
- [converter-id]-[option-name]-[2] (optional):
62
- This is an alternative, and simpler pattern than the above, for providing fallback for a single converter.
63
- If WebPOnDemand detects that such an option is provided (ie ewww-key-2=yyy), it will automatically insert an extra
64
- converter into the array (immidiately after), configured with the options with the '-2' postfix.
65
- Example: `...&converters=cwebp,ewww,gd&ewww-key=xxx&ewww-key-2=yyy`
66
- - will result in converter order: cwebp, ewww (with key=xxx), ewww (with key=yyy), gd
67
-
68
- converters (optional):
69
- Comma-separated list of converters. Ie. "cwebp,gd".
70
- Passing options to the individual converters is done by passing options named like this:
71
- [converter-name]-[option-name] (see below)
72
-
73
- See WebPConvert documentation for more info
74
-
75
- [converter]-[option-name] (optional):
76
- Options for the converters can be passed as parameters with names like this: [converter]-[option-name].
77
- Ie, in order to set the "key" option of the "ewww" converter, you pass "ewww-key".
78
-
79
- If the same converter is going to be used with different configurations, you can add "-[n]" after the converter id.
80
- Ie: ...&converters=ewww,ewww&ewww-key=xxx&ewww-2-key=yyy
81
-
82
- See WebPConvert documentation for more info
83
-
84
- debug (optional):
85
- If set, a report will be served (as text) instead of an image
86
-
87
- fail:
88
- Default: "original"
89
- What to serve if conversion fails
90
-
91
- Possible values:
92
- - "original": Serves the original image (source)
93
- - "404": Serves a 404 header
94
- - "report": Serves the error message as plain text
95
- - "report-as-image": Serves the error message as an image
96
-
97
- critical-fail:
98
- Default: "report-as-image"
99
- What to serve if conversion fails and source image is not available
100
-
101
- Possible values:
102
- - "404": Serves a 404 header
103
- - "report": Serves the error message as plain text
104
- - "report-as-image": Serves the error message as an image
105
-
106
- */
107
-
108
- namespace WebPOnDemand;
109
-
110
- use WebPConvertAndServe\WebPConvertAndServe;
111
- use WebPConvert\WebPConvert;
112
- use WebPConvert\Converters\ConverterHelper;
113
-
114
- class WebPOnDemand
115
- {
116
- // transform options with '-2' postfix into new converters
117
- // Idea: rename function to ie "transformFallbackOptionsIntoNewConverters"
118
- private static function transformFallbackOptions($converters) {
119
- foreach ($converters as $i => &$converter) {
120
- $duplicateConverter = false;
121
- foreach ($converter['options'] as $optionName => $optionValue) {
122
- if (substr($optionName, -2) === '-2') {
123
- $duplicateConverter = true;
124
- break;
125
- }
126
- }
127
- if ($duplicateConverter) {
128
- $options2 = [];
129
- foreach ($converter['options'] as $optionName => $optionValue) {
130
- if (substr($optionName, -2) === '-2') {
131
- $options2[substr($optionName, 0, -2)] = $optionValue;
132
- unset($converter['options'][$optionName]);
133
- }
134
- }
135
- array_splice($converters, $i+1, 0, [['converter' => $converter['converter'], 'options' => $options2]]);
136
- }
137
- }
138
- return $converters;
139
- }
140
-
141
- private static function setOption(&$array, $parameterName, $optionName, $optionType)
142
- {
143
- if (!isset($_GET[$parameterName])) {
144
- return;
145
- }
146
- switch ($optionType) {
147
- case 'string':
148
- //$options['converters'][$i]['options'][$optionName] = $_GET[$parameterName];
149
- $array[$optionName] = $_GET[$parameterName];
150
- break;
151
- case 'boolean':
152
- //$options['converters'][$i]['options'][$optionName] = ($_GET[$parameterName] == '1');
153
- $array[$optionName] = ($_GET[$parameterName] == '1');
154
- break;
155
- }
156
- }
157
- private static function removeDoubleSlash($str)
158
- {
159
- return preg_replace('/\/\//', '/', $str);
160
- }
161
- private static function getRelDir($from_dir, $to_dir)
162
- {
163
- $fromDirParts = explode('/', str_replace('\\', '/', $from_dir));
164
- $toDirParts = explode('/', str_replace('\\', '/', $to_dir));
165
- $i = 0;
166
- while (($i < count($fromDirParts)) && ($i < count($toDirParts)) && ($fromDirParts[$i] == $toDirParts[$i])) {
167
- $i++;
168
- }
169
- $rel = "";
170
- for ($j = $i; $j < count($fromDirParts); $j++) {
171
- $rel .= "../";
172
- }
173
-
174
- for ($j = $i; $j < count($toDirParts); $j++) {
175
- $rel .= $toDirParts[$j];
176
- if ($j < count($toDirParts)-1) {
177
- $rel .= '/';
178
- }
179
- }
180
- return $rel;
181
- }
182
-
183
- public static function serve($scriptPath)
184
- {
185
-
186
- $debug = (isset($_GET['debug']) ? ($_GET['debug'] != 'no') : false);
187
-
188
- //$source = $root . '/' . $_GET['source'];
189
-
190
- if (!isset($_GET['base-path'])) {
191
- $basePath = $scriptPath;
192
- } else {
193
- $basePath = $_GET['base-path'];
194
- if ((substr($basePath, 0, 1) == '/')) {
195
- } else {
196
- $basePath = $_SERVER["DOCUMENT_ROOT"] . '/' . $basePath;
197
- }
198
- }
199
-
200
- // Calculate $source and $sourceRelToBasePath (needed for calculating $destination)
201
- $sourcePath = $_GET['source']; // this path includes filename
202
- if ((substr($sourcePath, 0, 1) == '/')) {
203
- $sourcePathAbs = $sourcePath;
204
- $sourceRelToBasePath = self::getRelDir($basePath, $sourcePathAbs);
205
- //echo $basePath . '<br>' . $sourcePathAbs . '<br>' . $sourceRelToBasePath . '<br><br>';
206
-
207
- } else {
208
- $sourceRelToBasePath = $sourcePath;
209
- $sourcePathAbs = $basePath . '/' . $sourcePath;
210
- }
211
- $source = self::removeDoubleSlash($sourcePathAbs);
212
-
213
- // Calculate $destination from destination-root and $basePath
214
- if (!isset($_GET['destination-root'])) {
215
- $destinationRoot = '.';
216
- } else {
217
- $destinationRoot = $_GET['destination-root'];
218
- }
219
- if ((substr($destinationRoot, 0, 1) == '/')) {
220
- // absolute path - overrides basepath
221
- $destinationRootAbs = $destinationRoot;
222
- } else {
223
- $destinationRootAbs = $basePath . '/' . $destinationRoot;
224
- }
225
- $destination = self::removeDoubleSlash($destinationRootAbs . '/' . $sourceRelToBasePath . '.webp');
226
-
227
-
228
-
229
-
230
- $options = [];
231
-
232
- // quality
233
- if (isset($_GET['quality'])) {
234
- if ($_GET['quality'] == 'auto') {
235
- $options['quality'] = 'auto';
236
- } else {
237
- $options['quality'] = intval($_GET['quality']);
238
- }
239
- }
240
-
241
- // max-quality
242
- if (isset($_GET['max-quality'])) {
243
- $options['max-quality'] = intval($_GET['max-quality']);
244
- }
245
-
246
- // default-quality
247
- if (isset($_GET['default-quality'])) {
248
- $options['default-quality'] = intval($_GET['default-quality']);
249
- }
250
-
251
- // method
252
- if (isset($_GET['method'])) {
253
- $options['method'] = $_GET['method'];
254
- }
255
-
256
- // metadata
257
- if (isset($_GET['metadata'])) {
258
- $options['metadata'] = $_GET['metadata'];
259
- }
260
-
261
- // converters
262
- if (isset($_GET['converters'])) {
263
- $conv = explode(',', $_GET['converters']);
264
- $options['converters'] = [];
265
- foreach ($conv as $i => $converter_name) {
266
- $options['converters'][] = ['converter' => $converter_name, 'options' => []];
267
- }
268
- } else {
269
- // Copy default converters.
270
- // We need them in case some has options
271
- foreach (ConverterHelper::$defaultOptions['converters'] as $i => $converter_name) {
272
- $options['converters'][] = ['converter' => $converter_name, 'options' => []];
273
- }
274
- }
275
-
276
-
277
- // Converter options
278
- $counts = [];
279
- foreach ($options['converters'] as $i => $converter_object) {
280
- $converter = $converter_object['converter'];
281
- //echo $i . ':' . $converter;
282
- if (!isset($counts[$converter])) {
283
- $counts[$converter] = 1;
284
- $id = $converter;
285
- }
286
- else {
287
- $counts[$converter]++;
288
- }
289
-
290
- $className = ConverterHelper::getClassNameOfConverter($converter);
291
- $availOptions = array_column($className::$extraOptions, 'type', 'name');
292
- //print_r($availOptions);
293
-
294
- foreach ($availOptions as $optionName => $optionType) {
295
- $parameterName = $converter . (($counts[$converter] > 1 ? '-' . $counts[$converter] : '')) . '-' . $optionName;
296
-
297
- self::setOption($options['converters'][$i]['options'], $parameterName, $optionName, $optionType);
298
- self::setOption($options['converters'][$i]['options'], $parameterName . '-2', $optionName . '-2', $optionType);
299
-
300
- }
301
- }
302
-
303
- // transform options with '-2' postfix into new converters
304
- $options['converters'] = self::transformFallbackOptions($options['converters']);
305
-
306
- //echo '<pre>' . print_r($options, true) . '</pre>';
307
- // Failure actions
308
- $failCodes = [
309
- "original" => WebPConvertAndServe::$ORIGINAL,
310
- "404" => WebPConvertAndServe::$HTTP_404,
311
- "report-as-image" => WebPConvertAndServe::$REPORT_AS_IMAGE,
312
- "report" => WebPConvertAndServe::$REPORT,
313
- ];
314
-
315
- $fail = 'original';
316
- if (isset($_GET['fail'])) {
317
- $fail = $_GET['fail'];
318
- }
319
- $fail = $failCodes[$fail];
320
-
321
- $criticalFail = 'report';
322
- if (isset($_GET['critical-fail'])) {
323
- $criticalFail = $_GET['critical-fail'];
324
- }
325
- $criticalFail = $failCodes[$criticalFail];
326
-
327
- if (!$debug) {
328
- return WebPConvertAndServe::convertAndServeImage($source, $destination, $options, $fail, $criticalFail);
329
- } else {
330
-
331
- // TODO
332
- // As we do not want to leak api keys, I have commented out the following.
333
- /*
334
- echo 'GET parameters:<br>';
335
- foreach ($_GET as $key => $value) {
336
- echo '<i>' . $key . '</i>: ' . htmlspecialchars($value) . '<br>';
337
- }
338
- echo '<br>';*/
339
-
340
- //echo $_SERVER['DOCUMENT_ROOT'];
341
- WebPConvertAndServe::convertAndReport($source, $destination, $options);
342
- return 1;
343
- }
344
- }
345
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
webp-express.php CHANGED
@@ -3,117 +3,19 @@
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.4.0
7
  * Author: Bjørn Rosell
8
  * Author URI: https://www.bitwise-it.dk
9
  * License: GPL2
10
  */
11
 
12
-
13
  /*
14
  Note: Perhaps create a plugin page on my website?, ie https://www.bitwise-it.dk/software/wordpress/webp-express
15
  */
16
 
17
-
18
- /*
19
- // uncomment this block to debug an error during activation
20
- function tl_save_error() {
21
- update_option( 'webp-express-activation-error', ob_get_contents() );
22
- }
23
- add_action( 'activated_plugin', 'tl_save_error' );
24
- if (!empty(get_option('plugin_error'))) {
25
- add_filter( 'admin_footer_text', function() {
26
- return 'Activation error:' . get_option('webp-express-activation-error');
27
- });
28
- }*/
29
-
30
  define('WEBPEXPRESS_PLUGIN', __FILE__);
31
  define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
32
 
33
- if (empty(get_option('webp-express-configured'))) {
34
- global $wpdb;
35
- $hasWebPExpressOptionBeenSaved = ($wpdb->get_row( "SELECT * FROM $wpdb->options WHERE option_name = 'webp_express_converters'" ) !== null);
36
- if ($hasWebPExpressOptionBeenSaved) {
37
- // Store the fact that webp options has been changed.
38
- // When nobody is using 0.3 or below, we can test on the existence of that option, instead of
39
- // querying the database directly ($hasWebPExpressOptionBeenSaved)
40
- add_option('webp-express-configured', true);
41
- }
42
- }
43
-
44
- add_action( 'admin_menu', function() {
45
-
46
- //Add Settings Page
47
- add_options_page(
48
- 'WebP Express Settings', //Page Title
49
- __( 'WebP Express', 'yasr' ), //Menu Title
50
- 'manage_options', //capability
51
- 'webp_express_settings_page', //menu slug
52
- 'webp_express_settings_page_content' //The function to be called to output the content for this page.
53
- );
54
- });
55
-
56
- include(plugin_dir_path(__FILE__) . 'lib/options.php');
57
-
58
- if (get_option('webp-express-htaccess-needs-updating')) {
59
- delete_option('webp-express-htaccess-needs-updating');
60
- //include(plugin_dir_path(__FILE__) . 'lib/helpers.php');
61
- include_once 'lib/helpers.php';
62
-
63
- $rules = WebPExpressHelpers::generateHTAccessRules();
64
- WebPExpressHelpers::insertHTAccessRules($rules);
65
-
66
  }
67
-
68
-
69
- register_activation_hook(__FILE__, function () {
70
- include(plugin_dir_path(__FILE__) . 'lib/activate.php');
71
- });
72
-
73
- register_deactivation_hook(__FILE__, function () {
74
- include(plugin_dir_path(__FILE__) . 'lib/deactivate.php');
75
- });
76
-
77
- if (get_option('webp-express-message-pending')) {
78
- include(plugin_dir_path(__FILE__) . 'lib/message.php');
79
- }
80
-
81
- if (get_option('webp-express-deactivate')) {
82
- add_action('admin_init', function () {
83
- deactivate_plugins(plugin_basename(__FILE__));
84
- });
85
- delete_option('webp-express-deactivate');
86
- }
87
-
88
- function webp_express_register_uninstall_hook() {
89
- $optionsToDelete = [
90
- 'webp_express_max_quality',
91
- 'webp_express_image_types_to_convert',
92
- 'webp_express_failure_response',
93
- 'webp_express_converters',
94
- 'webp-express-inserted-rules-ok',
95
- 'webp-express-configured',
96
- ];
97
- foreach ($optionsToDelete as $i => $optionName) {
98
- delete_option($optionName);
99
- }
100
- /*
101
- webp_express_fail_action
102
- webp_express_method
103
- webp_express_quality
104
- */
105
- // Should we also call unregister_setting ?
106
- }
107
-
108
- // interestingly, I get "Serialization of 'Closure' is not allowed" if I pass anonymous function
109
- // ... perhaps we should not do that in the other hooks either.
110
- register_uninstall_hook( __FILE__, 'webp_express_register_uninstall_hook');
111
-
112
-
113
- // Add settings link on the plugins page
114
- add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), function ( $links ) {
115
- $mylinks = array(
116
- '<a href="' . admin_url( 'options-general.php?page=webp_express_settings_page' ) . '">Settings</a>',
117
- );
118
- return array_merge( $links, $mylinks );
119
- });
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
10
  */
11
 
 
12
  /*
13
  Note: Perhaps create a plugin page on my website?, ie https://www.bitwise-it.dk/software/wordpress/webp-express
14
  */
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  define('WEBPEXPRESS_PLUGIN', __FILE__);
17
  define('WEBPEXPRESS_PLUGIN_DIR', __DIR__);
18
 
19
+ if (is_admin()) {
20
+ include __DIR__ . '/lib/admin.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
webp-on-demand.php DELETED
@@ -1,17 +0,0 @@
1
- <?php
2
-
3
- error_reporting(E_ALL);
4
- ini_set('display_errors', 'On');
5
-
6
- //require 'webp-on-demand/vendor/autoload.php';
7
- //require 'vendor/webp-on-demand/autoload.php';
8
- require 'vendor/require-webp-on-demand.php';
9
-
10
- use WebPOnDemand\WebPOnDemand;
11
-
12
-
13
- $status = WebPOnDemand::serve(__DIR__);
14
- if ($status < 0) {
15
- // Conversion failed.
16
- // you could message your application about the problem here...
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{vendor/webp-convert/Converters → wod}/Binaries/cwebp-fbsd RENAMED
File without changes
{vendor/webp-convert/Converters → wod}/Binaries/cwebp-linux RENAMED
File without changes
{vendor/webp-convert/Converters → wod}/Binaries/cwebp-mac12 RENAMED
File without changes
{vendor/webp-convert/Converters → wod}/Binaries/cwebp-sol RENAMED
File without changes
{vendor/webp-convert/Converters → wod}/Binaries/cwebp.exe RENAMED
File without changes
wod/webp-convert-and-serve.inc ADDED
@@ -0,0 +1,1615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require 'webp-on-demand.inc';
4
+
5
+ use \WebPOnDemand\WebPOnDemand;
6
+
7
+ $options = [];
8
+
9
+ $contentDirAbs = $_SERVER['DOCUMENT_ROOT'] . '/' . $_GET['wp-content'] . '/webp-express';
10
+ $configFilename = $contentDirAbs . '/config/wod-options.json';
11
+ $handle = @fopen($configFilename, "r");
12
+ $json = fread($handle, filesize($configFilename));
13
+ fclose($handle);
14
+
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'])) {
22
+ $options['show-report'] = true;
23
+ }
24
+ if (isset($_GET['reconvert'])) {
25
+ $options['reconvert'] = true;
26
+ }
27
+ }
28
+ $source = $_GET['source'];
29
+
30
+ // Calculate destination
31
+ $applicationRoot = $_SERVER["DOCUMENT_ROOT"];
32
+ $imageRoot = $contentDirAbs . '/webp-images';
33
+
34
+ if (substr($source, 0, strlen($applicationRoot)) === $applicationRoot) {
35
+ // Source file is residing inside document root.
36
+ // We can store relative to that.
37
+ $sourceRel = substr($source, strlen($applicationRoot));
38
+ $destination = $imageRoot . '/doc-root' . $sourceRel . '.webp';
39
+ } else {
40
+ // Source file is residing outside document root.
41
+ // we must add complete path to structure
42
+ $destination = $imageRoot . '/abs' . $source . '.webp';
43
+ }
44
+ //$destination = $imageRoot . $source . '.webp';
45
+
46
+
47
+ //echo $source . '<br>';
48
+ //echo $destination . '<br>';
49
+ //echo $sourceRel;
50
+ WebPOnDemand::serve($source, $destination, $options);