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('/[\/\\\\][^\/\\\\]*#x2F;', '', $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('/\/#x2F;', '', $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