Version Description
(released: 17 sep 2019)
- Provided test-buttons for checking if the redirects works.
- You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
- You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
- WebP Express can now handle when wp-content is symlinked.
- The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
- Added option to convert only PNG files
- And a couple of bugfixes.
For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/22?closed=1
Download this release
Release Info
Developer | rosell.dk |
Plugin | WebP Express |
Version | 0.15.0 |
Comparing to | |
See all releases |
Code changes from version 0.14.22 to 0.15.0
- BACKERS.md +1 -0
- README.md +95 -106
- README.txt +109 -98
- changelog.txt +161 -0
- composer.lock +5 -5
- docs/development.md +2 -15
- lib/classes/AdminInit.php +3 -2
- lib/classes/AlterHtmlHelper.php +37 -32
- lib/classes/BulkConvert.php +52 -27
- lib/classes/CacheMover.php +57 -5
- lib/classes/CachePurge.php +4 -0
- lib/classes/CapabilityTest.php +6 -0
- lib/classes/Config.php +75 -10
- lib/classes/Convert.php +40 -8
- lib/classes/ConvertHelperIndependent.php +306 -71
- lib/classes/FileHelper.php +31 -0
- lib/classes/HTAccess.php +120 -565
- lib/classes/HTAccessRules.php +855 -0
- lib/classes/HandleUploadHooks.php +1 -1
- lib/classes/ImageRoot.php +50 -0
- lib/classes/ImageRoots.php +49 -0
- lib/classes/Mime.php +15 -8
- lib/classes/PathHelper.php +344 -0
- lib/classes/Paths.php +351 -95
- lib/classes/PlatformInfo.php +64 -15
- lib/classes/PluginActivate.php +3 -2
- lib/classes/PluginDeactivate.php +9 -6
- lib/classes/SanityCheck.php +123 -43
- lib/classes/SelfTest.php +118 -0
- lib/classes/SelfTestHelper.php +535 -0
- lib/classes/SelfTestRedirectAbstract.php +115 -0
- lib/classes/SelfTestRedirectToConverter.php +208 -0
- lib/classes/SelfTestRedirectToExisting.php +253 -0
- lib/classes/SelfTestRedirectToWebPRealizer.php +228 -0
- lib/classes/WebPOnDemand.php +229 -0
- lib/classes/WebPRealizer.php +253 -0
- lib/classes/WodConfigLoader.php +180 -0
- lib/dismissable-messages/0.15.0/new-scope-setting-content.php +12 -0
- lib/dismissable-messages/0.15.0/new-scope-setting-index.php +13 -0
- lib/dismissable-messages/0.15.0/new-scope-setting-no-uploads.php +12 -0
- lib/migrate/migrate11.php +77 -0
- lib/migrate/migrate7.php +1 -1
- lib/options/css/webp-express-options-page.css +56 -2
- lib/options/enqueue_scripts.php +29 -11
- lib/options/js/0.14.9/escapeHTML.js +0 -16
- lib/options/js/{0.14.9 → 0.15.0}/authorized_sites_bak.js +0 -0
- lib/options/js/{0.14.9 → 0.15.0}/bulk-convert.js +18 -13
- lib/options/js/{0.14.9 → 0.15.0}/converters.js +0 -45
- lib/options/js/{0.14.9 → 0.15.0}/das-popup.js +0 -0
- lib/options/js/0.15.0/escapeHTML.js +34 -0
- lib/options/js/{0.14.9 → 0.15.0}/image-comparison-slider.js +0 -0
- lib/options/js/{0.14.9 → 0.15.0}/page.js +20 -7
- lib/options/js/{0.14.9 → 0.15.0}/purge-cache.js +1 -1
- lib/options/js/0.15.0/self-test.js +156 -0
- lib/options/js/{0.14.9 → 0.15.0}/sortable.min.js +0 -0
- lib/options/js/{0.14.9 → 0.15.0}/test-convert.js +8 -2
- lib/options/js/{0.14.9 → 0.15.0}/whitelist.js +0 -0
- lib/options/options/conversion-options/jpeg.inc +1 -1
- lib/options/options/general/destination-extension.inc +2 -1
- lib/options/options/general/destination-structure.inc +38 -0
- lib/options/options/general/general.inc +11 -0
- lib/options/options/general/image-types.inc +1 -0
- lib/options/options/general/scope.inc +59 -0
- lib/options/options/redirection-rules/enable-redirection-to-converter.inc +3 -0
- lib/options/options/redirection-rules/enable-redirection-to-webp-realizer.inc +4 -1
- lib/options/options/redirection-rules/redirect-to-existing.inc +4 -0
- lib/options/options/redirection-rules/redirection-rules.inc +9 -3
- lib/options/page-messages.php +2 -2
- lib/options/page.php +5 -1
- lib/options/submit.php +111 -18
- vendor/composer/autoload_classmap.php +0 -83
- vendor/composer/autoload_static.php +0 -87
- vendor/composer/installed.json +6 -6
- vendor/rosell-dk/webp-convert/.php_cs.dist +0 -19
- vendor/rosell-dk/webp-convert/BACKERS.md +1 -0
- vendor/rosell-dk/webp-convert/README.md +12 -7
- vendor/rosell-dk/webp-convert/composer.json +1 -18
- vendor/rosell-dk/webp-convert/phpdox.xml +0 -9
- vendor/rosell-dk/webp-convert/phpstan.neon +0 -28
- vendor/rosell-dk/webp-convert/phpunit.xml.dist +0 -41
- vendor/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php +6 -0
- vendor/rosell-dk/webp-convert/src/Helpers/Sanitize.php +30 -0
- vendor/rosell-dk/webp-convert/src/Options/Options.php +11 -2
- webp-express.php +1 -1
- wod/webp-on-demand.php +11 -219
- wod/webp-realizer.php +12 -207
BACKERS.md
CHANGED
@@ -13,6 +13,7 @@ To become a backer, yourself, [go to my page at patreon.com](https://www.patreon
|
|
13 |
| Name | Date | Message (max 70 chars, plain text only) |
|
14 |
| --------------------- | ---------- | ----------------------------------------------------------------------- |
|
15 |
| Tammy Valgardson | 2018-12-27 | |
|
|
|
16 |
|
17 |
<sub>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</sub>
|
18 |
|
13 |
| Name | Date | Message (max 70 chars, plain text only) |
|
14 |
| --------------------- | ---------- | ----------------------------------------------------------------------- |
|
15 |
| Tammy Valgardson | 2018-12-27 | |
|
16 |
+
| Max Kreminsky | 2019-08-02 | |
|
17 |
|
18 |
<sub>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</sub>
|
19 |
|
README.md
CHANGED
@@ -12,12 +12,8 @@ But well, it is developed ([here on github](https://github.com/rosell-dk/webp-ex
|
|
12 |
## Description
|
13 |
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
|
14 |
|
15 |
-
**Security notice**
|
16 |
-
Security issues has been found and fixed. I urge you to upgrade to the latest release (at least 0.14.11, but go with 0.14.15, as there are important bug fixes)
|
17 |
-
|
18 |
-
|
19 |
### The image converter
|
20 |
-
The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `cwebp`, `
|
21 |
|
22 |
### The "Serving webp to browsers that supports it" part.
|
23 |
|
@@ -27,16 +23,21 @@ The plugin supports different ways of delivering webps to browsers that supports
|
|
27 |
2. By altering the HTML, replacing image tags with *picture* tags. Missing webps are auto generated upon visit.
|
28 |
3. By altering the HTML, replacing image URLs so all points to webp. The replacements only being made for browsers that supports webp. Again, missing webps are auto generated upon visit.
|
29 |
4. In combination with *Cache Enabler*, the same as above can be achieved, but with page caching.
|
30 |
-
5. You can also deliver webp to *all* browsers and add the [webpjs](http://webpjs.appspot.com) javascript, which provides webp support for browsers that doesn't support webp natively.
|
31 |
|
32 |
-
The plugin
|
|
|
|
|
|
|
|
|
33 |
|
34 |
### Benefits
|
35 |
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
|
36 |
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
|
37 |
- Better ranking in Google searches (performance is taken into account by Google)
|
38 |
- Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
|
39 |
-
- Currently ~
|
|
|
40 |
|
41 |
### Recent news
|
42 |
Feb 2019: Multisite is now supported (0.12.0)
|
@@ -68,20 +69,23 @@ Here you have all options available.
|
|
68 |
|
69 |
|
70 |
#### Conversion methods
|
71 |
-
WebP Express has a bunch of methods available for converting images: Executing cwebp binary, Gd extension, Imagick extension, ewww cloud converter and remote WebP express. Each requires *something*. In many cases, one of the conversion methods will be available. You can quickly identify which converters are working - there is a green icon next to them. Hovering conversion methods that are not working will show you what is wrong.
|
72 |
|
73 |
In case no conversion methods are working out of the box, you have several options:
|
74 |
- You can install this plugin on another website, which supports a local conversion method and connect to that using the "Remote WebP Express" conversion method
|
75 |
- You can [purchase a key](https://ewww.io/plans/) for the ewww cloud converter. They do not charge credits for webp conversions, so all you ever have to pay is the one dollar start-up fee :)
|
76 |
- You can set up [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) on another server and connect to that. Its open source.
|
77 |
-
- You can try to meet the server requirements of cwebp,
|
|
|
|
|
|
|
78 |
|
79 |
-
|
80 |
-
If your server has imagick og gmagick installed, the plugin will be able to detect the quality of a jpeg, and use the same quality for the converted webp. You can tell if it does, by looking at the quality option. If it allows you to select "auto" quality, it is available, otherwise it is not, and you will only have the option to set a specific quality for all conversions. *Auto* should be chosen, if available, as this ensures that each conversion are converted with an appropriate quality. Say you have a jpeg with low quality (say 30). The best result, is achieved by converting it to the same quality. Converting it with high quality (say 80), will not get you better quality, only a larger file.
|
81 |
|
82 |
-
If you do not
|
83 |
-
- Install
|
84 |
-
-
|
|
|
85 |
- If you have cwebp converter available, you can configure it to aim for a certain reduction, rather than using the quality parameter. Set this to for example 50%, or even 45%.
|
86 |
|
87 |
### Verifying that it works.
|
@@ -146,72 +150,36 @@ Easy enough. Browsers looks at the *content type* header rather than the URL to
|
|
146 |
|
147 |
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
|
148 |
|
149 |
-
### I am on NGINX
|
|
|
|
|
150 |
The easy solution is simply to use the plugin in "CDN friendly" mode, do a bulk conversion (takes care of converting existing images) and activate the "Convert on upload" option (takes care of converting new images in the media library).
|
151 |
|
152 |
-
|
|
|
|
|
153 |
|
154 |
-
|
|
|
155 |
|
156 |
-
There are two different approaches to achieve the redirections.
|
157 |
|
158 |
For multisite on NGINX, read [here](https://github.com/rosell-dk/webp-express/issues/8)
|
159 |
|
160 |
-
####
|
161 |
-
|
162 |
-
Lets take this step by step.
|
163 |
-
First step is to redirect images to the script. Second step is redirecting directly to existing webp images. Finally, as an optional third step, you can add extra rules for enabling the *Convert non-existing webp-files upon request* functionality
|
164 |
|
165 |
-
|
166 |
-
|
167 |
-
The following will redirect all images under wp-content to the script, but only for webp-enabled browsers.
|
168 |
|
|
|
169 |
Insert the following in the `server` context of your configuration file (usually found in `/etc/nginx/sites-available`). "The `server` context" refers to the part of the configuration that starts with "server {" and ends with the matching "}".
|
170 |
|
171 |
-
```nginx
|
172 |
-
location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
173 |
-
add_header Vary Accept;
|
174 |
-
if ($http_accept !~* "webp"){
|
175 |
-
break;
|
176 |
-
}
|
177 |
-
try_files
|
178 |
-
/nonexisting-because-try-files-needs-fallback
|
179 |
-
/wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content
|
180 |
-
;
|
181 |
-
}
|
182 |
-
```
|
183 |
-
*Beware when copy/pasting: You might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded*
|
184 |
-
|
185 |
-
If you have moved wp-content to a non-standard place, you must change accordingly. Especially note that you must also change the wp-content parameter to the script. It expects a relative path to wp-content (from document root) and is needed so the script can find the configuration file.
|
186 |
-
|
187 |
-
The `xsource` parameter helps the script finding the source file. It is only needed on some setups. You can try deleting it and see if it still works.
|
188 |
-
|
189 |
-
Beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g", so the first line becomes:
|
190 |
-
```nginx
|
191 |
-
location ~* ^/?wp-content/.*\.jpe?g$ {
|
192 |
-
```
|
193 |
-
|
194 |
-
If you cannot get this to work then perhaps you need to add the following to your `mime.types` configuration file:
|
195 |
-
`image/webp webp;`
|
196 |
-
|
197 |
-
If you still cannot get it to work, you can instead try *method 2*
|
198 |
-
|
199 |
-
Note: There is no longer any reason to add "&$args" to the line begining with "/wp-content". It was there to enable debugging a single image by appending "?debug" to the url. I however removed that functionality from `webp-on-demand.php`.
|
200 |
-
|
201 |
-
**Step 2**: *Redirecting directly to existing webp images.*
|
202 |
-
|
203 |
-
Once you got this working, lets improve performance by redirecting directly to existing webp images. This step isn't necessary, as the script also does that - but invoking the php script takes more resources that the direct redirect. Also, a direct redirect will produce *ETag* response header, which is increases caching performance.
|
204 |
-
|
205 |
-
The rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you must configure *WebP Express* to store the converted files like that.
|
206 |
-
|
207 |
-
The following rules works both when WebP Express are configured to store images in the same folder as the originals ("mingled") and when or in a separate folder. Nginx will first see if there is a corresponding webp in the separate folder and then if there is a corresponding webp in the same folder and finally fall back to calling the conversion script.
|
208 |
-
|
209 |
```nginx
|
210 |
# WebP Express rules
|
211 |
# --------------------
|
212 |
location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
213 |
add_header Vary Accept;
|
214 |
-
|
215 |
if ($http_accept !~* "webp"){
|
216 |
break;
|
217 |
}
|
@@ -221,53 +189,51 @@ location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
|
221 |
/wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content
|
222 |
;
|
223 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
# ------------------- (WebP Express rules ends here)
|
225 |
```
|
226 |
-
*Beware when copy/pasting: You might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded*
|
227 |
|
228 |
-
|
|
|
229 |
|
230 |
-
|
231 |
|
232 |
-
|
233 |
|
234 |
-
|
235 |
-
In most cases you can and should allow images to be cached for a long period. To do that, simply uncomment the "expires 365d;" line, by removing the "#" in front of it.
|
236 |
|
237 |
-
|
238 |
|
239 |
-
|
240 |
|
241 |
-
|
242 |
-
location ~* ^/?wp-content/.*\.(png|jpe?g)\.webp$ {
|
243 |
-
try_files
|
244 |
-
$uri
|
245 |
-
/wp-content/plugins/webp-express/wod/webp-realizer.php?wp-content=wp-content&$args
|
246 |
-
;
|
247 |
-
}
|
248 |
-
```
|
249 |
|
250 |
-
|
|
|
251 |
|
|
|
252 |
|
253 |
-
|
254 |
|
255 |
-
|
256 |
|
257 |
-
|
258 |
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
|
264 |
-
}
|
265 |
-
# ------------------- (WebP Express rules ends here)
|
266 |
-
```
|
267 |
|
268 |
-
|
|
|
269 |
|
270 |
-
Make sure that WebP Express is configured with "Destination" set to "Mingled". And then insert the following in the `server` context (replacing the rules you inserted in step 1)
|
271 |
```nginx
|
272 |
# WebP Express rules
|
273 |
# --------------------
|
@@ -295,17 +261,22 @@ if ($whattodo = A) {
|
|
295 |
}
|
296 |
# ------------------- (WebP Express rules ends here)
|
297 |
```
|
298 |
-
*Beware when copy/pasting: You might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded*
|
299 |
|
300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
301 |
|
302 |
-
|
303 |
-
1. Set *Destination folder* to *mingled*
|
304 |
-
2. Set *File extension* to *Append ".webp"*
|
305 |
|
306 |
-
|
307 |
|
308 |
-
|
309 |
|
310 |
It is possible to put this stuff inside a `location` directive. However, having `if` directives inside `location` directives [is considered evil](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/). But it seems that in our case, it works. If you wish to do that, use the following rules instead:
|
311 |
|
@@ -338,12 +309,22 @@ location ~* ^/wp-content/.*\.webp$ {
|
|
338 |
}
|
339 |
# ------------------- (WebP Express rules ends here)
|
340 |
```
|
341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
342 |
|
343 |
Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
|
344 |
And here: https://github.com/rosell-dk/webp-express/issues/166
|
345 |
|
346 |
-
Here are rules if you need to replace the file extension with ".webp" rather than appending ".webp" to it: https://www.keycdn.com/support/optimus/configuration-to-deliver-webp
|
347 |
|
348 |
### I am on a Windows server
|
349 |
Good news! It should work now, thanks to a guy that calls himself lwxbr. At least on XAMPP 7.3.1, Windows 10. https://github.com/rosell-dk/webp-express/pull/213.
|
@@ -526,7 +507,7 @@ If you do not want to use serve varied images:
|
|
526 |
- Open WebP Express options
|
527 |
- Switch to *CDN friendly* mode.
|
528 |
- Set *File extension* to "Set to .webp"
|
529 |
-
- Enable *Alter HTML* and select *Replace image URLs*. It is not absolutely
|
530 |
- If you enabled *Alter HTML*, also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*
|
531 |
- If you did not enable *Alter HTML*, enable *Convert non-existing webp-files upon request to original image*
|
532 |
|
@@ -610,6 +591,15 @@ Here are my current plans ahead: 0.15 will probably be a file manager-like inter
|
|
610 |
|
611 |
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
|
612 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
613 |
## Changes in 0.14.12 - 0.14.15
|
614 |
- Fixed errors with "redirect to conversion script" on systems with symlinked folders
|
615 |
- Fixed errors with "redirect to conversion script" on systems where the filename cannot be passed through an environment variable
|
@@ -624,7 +614,6 @@ It is urged that you upgrade all of you WebP Express installations!
|
|
624 |
- Security fix: Added checks for file paths and directories.
|
625 |
- Security fix: Nonces and capability checks for AJAX calls.
|
626 |
|
627 |
-
|
628 |
## Changes in 0.14.4
|
629 |
- Now bundles with multiple cwebp binaries for linux for systems where 1.0.2 fails.
|
630 |
|
12 |
## Description
|
13 |
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
|
14 |
|
|
|
|
|
|
|
|
|
15 |
### The image converter
|
16 |
+
The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
|
17 |
|
18 |
### The "Serving webp to browsers that supports it" part.
|
19 |
|
23 |
2. By altering the HTML, replacing image tags with *picture* tags. Missing webps are auto generated upon visit.
|
24 |
3. By altering the HTML, replacing image URLs so all points to webp. The replacements only being made for browsers that supports webp. Again, missing webps are auto generated upon visit.
|
25 |
4. In combination with *Cache Enabler*, the same as above can be achieved, but with page caching.
|
26 |
+
5. You can also deliver webp to *all* browsers and add the [webpjs](http://webpjs.appspot.com) javascript, which provides webp support for browsers that doesn't support webp natively. However, beware that the javascript doesn't support srcset attributes, which is why I haven't added that method to the plugin (yet).
|
27 |
|
28 |
+
The plugin implements the "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/webp-on-demand.md) and builds on a bunch of open source libraries (all maintained by me):
|
29 |
+
- [WebPConvert](https://github.com/rosell-dk/webp-convert): For converting images to webp
|
30 |
+
- [WebP Convert Cloud Service](https://github.com/rosell-dk/webp-convert-cloud-service): For the Web Service functionality
|
31 |
+
- [DOM Util for WebP](https://github.com/rosell-dk/dom-util-for-webp): For the Alter HTML functionality
|
32 |
+
- [Image MimeType Guesser](https://github.com/rosell-dk/image-mime-type-guesser): For detecting mime types of images.
|
33 |
|
34 |
### Benefits
|
35 |
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
|
36 |
- Better user experience (whether performance goes from terrible to bad, or from good to impressive, it is a benefit)
|
37 |
- Better ranking in Google searches (performance is taken into account by Google)
|
38 |
- Less bandwidth consumption - makes a huge difference in the parts of the world where the internet is slow and costly (you know, ~80% of the world population lives under these circumstances).
|
39 |
+
- Currently ~83% of all traffic, and ~80% of mobile browsing traffic are done with browsers supporting webp. With Mozilla and Microsoft [finally on board](https://medium.com/@richard_90141/webp-image-support-an-8-year-saga-7aa2bedb8d02), these numbers are bound to increase. Check current numbers on [caniuse.com](https://caniuse.com/webp)).
|
40 |
+
- It's great for the environment too! Reducing network traffic reduces electricity consumption which reduces CO2 emissions.
|
41 |
|
42 |
### Recent news
|
43 |
Feb 2019: Multisite is now supported (0.12.0)
|
69 |
|
70 |
|
71 |
#### Conversion methods
|
72 |
+
WebP Express has a bunch of methods available for converting images: Executing cwebp binary, Gd extension, Imagick extension, Vips extension, ewww cloud converter and remote WebP express etc. Each requires *something*. In many cases, one of the conversion methods will be available. You can quickly identify which converters are working - there is a green icon next to them. Hovering conversion methods that are not working will show you what is wrong.
|
73 |
|
74 |
In case no conversion methods are working out of the box, you have several options:
|
75 |
- You can install this plugin on another website, which supports a local conversion method and connect to that using the "Remote WebP Express" conversion method
|
76 |
- You can [purchase a key](https://ewww.io/plans/) for the ewww cloud converter. They do not charge credits for webp conversions, so all you ever have to pay is the one dollar start-up fee :)
|
77 |
- You can set up [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service) on another server and connect to that. Its open source.
|
78 |
+
- You can try to meet the server requirements of cwebp, Gd, Imagick, Gmagick or Vips. Check out [this wiki page](https://github.com/rosell-dk/webp-convert/wiki/Meeting-the-requirements-of-the-converters) on how to do that
|
79 |
+
|
80 |
+
### Quality detection of jpegs
|
81 |
+
If your server has Imagick extension or is able to execute imagemagick binary, the plugin will be able to detect the quality of a jpeg, and use that quality for the converted webp. You can tell if the quality detection is available by hovering the help icon in Conversion > Jpeg options > Quality for lossy. The last line in that help text tells you.
|
82 |
|
83 |
+
This auto quality has benefits over fixed quality as it ensures that each conversion are converted with an appropriate quality. Encoding low quality jpegs to high quality webps does not magically increase the visual quality so that your webp looks better than the original. But it does result in a much larger filesize than if the jpeg where converting to a webp with the same quality setting as the original.
|
|
|
84 |
|
85 |
+
If you do not have quality detection working, you can try one of the following:
|
86 |
+
- Install Imagick on the server (for this purpose, it is not required that it is compiled with WebP support)
|
87 |
+
- Install imagemagick on the server and grant permission for PHP to use the "exec" function.
|
88 |
+
- Use "Remote WebP Express" converter to connect to a site, that *does* have quality detection working
|
89 |
- If you have cwebp converter available, you can configure it to aim for a certain reduction, rather than using the quality parameter. Set this to for example 50%, or even 45%.
|
90 |
|
91 |
### Verifying that it works.
|
150 |
|
151 |
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
|
152 |
|
153 |
+
### I am on NGINX or OpenResty
|
154 |
+
|
155 |
+
#### The simple way (no redirecting rules)
|
156 |
The easy solution is simply to use the plugin in "CDN friendly" mode, do a bulk conversion (takes care of converting existing images) and activate the "Convert on upload" option (takes care of converting new images in the media library).
|
157 |
|
158 |
+
*PRO*: Very easy to set up.
|
159 |
+
*CON*: Images in external CSS and images being dynamically added with javascript will not be served as webp.
|
160 |
+
*CON*: New new theme images will not be converted until you run a new Bulk conversion
|
161 |
|
162 |
+
#### The advanced way (creating NGINX redirecting rules)
|
163 |
+
Creating NGINX rules requires manually inserting redirection rules in the NGINX configuration file (nginx.conf or the configuration file for the site, found in `/etc/nginx/sites-available`). If you do not have access to do that, you will have to settle with the "simple way" described above.
|
164 |
|
165 |
+
There are two different approaches to achieve the redirections. The one that I recommend is based on a *try_files* directive. If that doesn't work for you, you can try the alternative rules that are based on the *rewrite* directive. The rules are described in the next couple of sections.
|
166 |
|
167 |
For multisite on NGINX, read [here](https://github.com/rosell-dk/webp-express/issues/8)
|
168 |
|
169 |
+
#### Recommended rules (using "try_files")
|
|
|
|
|
|
|
170 |
|
171 |
+
__Preparational step:__
|
172 |
+
The rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you must configure *WebP Express* to store the converted files like that by setting *General > File extension* to *Append ".webp"*
|
|
|
173 |
|
174 |
+
__The rules:__
|
175 |
Insert the following in the `server` context of your configuration file (usually found in `/etc/nginx/sites-available`). "The `server` context" refers to the part of the configuration that starts with "server {" and ends with the matching "}".
|
176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
```nginx
|
178 |
# WebP Express rules
|
179 |
# --------------------
|
180 |
location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
181 |
add_header Vary Accept;
|
182 |
+
expires 365d;
|
183 |
if ($http_accept !~* "webp"){
|
184 |
break;
|
185 |
}
|
189 |
/wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content
|
190 |
;
|
191 |
}
|
192 |
+
|
193 |
+
# Route requests for non-existing webps to the converter
|
194 |
+
location ~* ^/?wp-content/.*\.(png|jpe?g)\.webp$ {
|
195 |
+
try_files
|
196 |
+
$uri
|
197 |
+
/wp-content/plugins/webp-express/wod/webp-realizer.php?wp-content=wp-content
|
198 |
+
;
|
199 |
+
}
|
200 |
# ------------------- (WebP Express rules ends here)
|
201 |
```
|
|
|
202 |
|
203 |
+
__BEWARE:__
|
204 |
+
- Beware that when copy/pasting you might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded (in the last line in the try_files block)
|
205 |
|
206 |
+
- Beware that the rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you __must__ configure *WebP Express* to store the converted files like that.
|
207 |
|
208 |
+
- Beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g".
|
209 |
|
210 |
+
- Beware that if you have moved wp-content to a non-standard place, you must change accordingly. Note that you must then also change the "wp-content" parameter to the script. It expects a relative path to wp-content (from document root) and is needed so the script can find the configuration file.
|
|
|
211 |
|
212 |
+
- I have put in an expires statement for caching. You might want to modify or disable that.
|
213 |
|
214 |
+
- The rules contains all redirections (as if you enabled all three redirection options in settings). If you do not wish to redirect to converter, remove the last line in the try_files block. If you do not wish to create webp files upon request, remove the last location block.
|
215 |
|
216 |
+
- If you have configured WebP Express to store images in separate folder, you do not need the "$uri.webp" line in the first "try_files" block. But it doesn't hurt to have it. And beware that the reverse is not true. If configured to store images in the same folder ("mingled"), you still need the line that looks for a webp in the separate folder. The reason for this is that the "mingled" only applies to the images in the upload folder - other images - such as theme images are always stored in a separate folder.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
|
218 |
+
If you cannot get this to work then perhaps you need to add the following to your *mime.types* configuration file:
|
219 |
+
`image/webp webp;`
|
220 |
|
221 |
+
If you still cannot get it to work, you can instead try the alternative rules below.
|
222 |
|
223 |
+
Credits: These rules are builds upon [Eugene Lazutkins solution](http://www.lazutkin.com/blog/2014/02/23/serve-files-with-nginx-conditionally/).
|
224 |
|
225 |
+
#### Alternative rules (using "rewrite")
|
226 |
|
227 |
+
In case the recommended rules does not work for you, you can try these alternative rules.
|
228 |
|
229 |
+
The reason I recommend the *try_files* approach above over these alternative rules is that it is a bit simpler and it is supposed to perform marginally better. These alternative rules are in no way inferior to the other. Choose whatever works!
|
230 |
+
|
231 |
+
__Preparational step:__
|
232 |
+
The rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you must configure *WebP Express* to store the converted files like that by setting *General > File extension* to *Append ".webp"*. Also make sure that WebP Express is configured with "Destination" set to "Mingled".
|
|
|
|
|
|
|
|
|
233 |
|
234 |
+
__The rules:__
|
235 |
+
Insert the following in the `server` context of your configuration file (usually found in `/etc/nginx/sites-available`). "The `server` context" refers to the part of the configuration that starts with "server {" and ends with the matching "}".
|
236 |
|
|
|
237 |
```nginx
|
238 |
# WebP Express rules
|
239 |
# --------------------
|
261 |
}
|
262 |
# ------------------- (WebP Express rules ends here)
|
263 |
```
|
|
|
264 |
|
265 |
+
__BEWARE:__
|
266 |
+
|
267 |
+
- Beware that when copy/pasting you might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded (in the last line in the try_files block)
|
268 |
+
|
269 |
+
- Beware that the rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you __must__ configure *WebP Express* to store the converted files like that.
|
270 |
+
|
271 |
+
- Beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g".
|
272 |
+
|
273 |
+
- Beware that if you have moved wp-content to a non-standard place, you must change accordingly. Note that you must then also change the "wp-content" parameter to the script. It expects a relative path to wp-content (from document root) and is needed so the script can find the configuration file.
|
274 |
|
275 |
+
- I have put in an expires statement for caching. You might want to modify or disable that.
|
|
|
|
|
276 |
|
277 |
+
- I have not set any expire on the webp-on-demand.php request. This is not needed, as the script sets this according to what you set up in WebP Express settings. Also, trying to do it would require a new location block matching webp-on-demand.php, but that would override the location block handling php files, and thus break the functionality.
|
278 |
|
279 |
+
- There is no longer any reason to add "&$args" to the line begining with "/wp-content". It was there to enable debugging a single image by appending "?debug" to the url. I however removed that functionality from `webp-on-demand.php`.
|
280 |
|
281 |
It is possible to put this stuff inside a `location` directive. However, having `if` directives inside `location` directives [is considered evil](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/). But it seems that in our case, it works. If you wish to do that, use the following rules instead:
|
282 |
|
309 |
}
|
310 |
# ------------------- (WebP Express rules ends here)
|
311 |
```
|
312 |
+
|
313 |
+
PS: In case you only want to redirect images to the script (and not to existing), the rules becomes much simpler:
|
314 |
+
|
315 |
+
```nginx
|
316 |
+
# WebP Express rules
|
317 |
+
# --------------------
|
318 |
+
if ($http_accept ~* "webp"){
|
319 |
+
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
|
320 |
+
}
|
321 |
+
# ------------------- (WebP Express rules ends here)
|
322 |
+
```
|
323 |
|
324 |
Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
|
325 |
And here: https://github.com/rosell-dk/webp-express/issues/166
|
326 |
|
327 |
+
Here are rules if you need to *replace* the file extension with ".webp" rather than appending ".webp" to it: https://www.keycdn.com/support/optimus/configuration-to-deliver-webp
|
328 |
|
329 |
### I am on a Windows server
|
330 |
Good news! It should work now, thanks to a guy that calls himself lwxbr. At least on XAMPP 7.3.1, Windows 10. https://github.com/rosell-dk/webp-express/pull/213.
|
507 |
- Open WebP Express options
|
508 |
- Switch to *CDN friendly* mode.
|
509 |
- Set *File extension* to "Set to .webp"
|
510 |
+
- Enable *Alter HTML* and select *Replace image URLs*. It is not absolutely necessary, as Cache Enabler also alters HTML - but there are several reasons to do it. Firstly, *Cache Enabler* doesn't get as many URLs replaced as we do. WebP Express for example also replaces background urls in inline styles. Secondly, *Cache enabler* has [problems in edge cases](https://regexr.com/46isf). Thirdly, WebP Express can be configured to alter HTML to point to corresponding webp images, *before they even exists* which can be used in conjunction with the the *Convert non-existing webp-files upon request* option. And this is smart, because then you don't have trouble with *Cache Enabler* caching HTML which references the original images due to that some images hasn't been converted yet.
|
511 |
- If you enabled *Alter HTML*, also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*
|
512 |
- If you did not enable *Alter HTML*, enable *Convert non-existing webp-files upon request to original image*
|
513 |
|
591 |
|
592 |
If you wish to affect priorities, it is certainly possible. You can try to argue your case in the forum or you can simply let the money do the talking. By donating as little as a cup of coffee on [ko-fi.com/rosell](https://ko-fi.com/rosell), you can leave a wish. I shall take these wishes into account when prioritizing between new features.
|
593 |
|
594 |
+
## Changes in 0.15.0
|
595 |
+
- Provided test-buttons for checking if the redirects works.
|
596 |
+
- You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
|
597 |
+
- You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
|
598 |
+
- WebP Express can now handle when wp-content is symlinked.
|
599 |
+
- The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
|
600 |
+
- Added option to convert only PNG files
|
601 |
+
- And a couple of bugfixes.
|
602 |
+
|
603 |
## Changes in 0.14.12 - 0.14.15
|
604 |
- Fixed errors with "redirect to conversion script" on systems with symlinked folders
|
605 |
- Fixed errors with "redirect to conversion script" on systems where the filename cannot be passed through an environment variable
|
614 |
- Security fix: Added checks for file paths and directories.
|
615 |
- Security fix: Nonces and capability checks for AJAX calls.
|
616 |
|
|
|
617 |
## Changes in 0.14.4
|
618 |
- Now bundles with multiple cwebp binaries for linux for systems where 1.0.2 fails.
|
619 |
|
README.txt
CHANGED
@@ -4,7 +4,7 @@ Donate link: https://ko-fi.com/rosell
|
|
4 |
Tags: webp, images, performance
|
5 |
Requires at least: 4.0
|
6 |
Tested up to: 5.2
|
7 |
-
Stable tag: 0.
|
8 |
Requires PHP: 5.6
|
9 |
License: GPLv3
|
10 |
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
@@ -15,12 +15,8 @@ Serve autogenerated WebP images instead of jpeg/png to browsers that supports We
|
|
15 |
|
16 |
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
|
17 |
|
18 |
-
!! **SECURITY NOTICE** !!
|
19 |
-
Security issues has recently been found and fixed. I urge you to upgrade to the latest release (at least 0.14.11, but go with 0.14.22, as there are important bug fixes)
|
20 |
-
|
21 |
-
|
22 |
### The image converter
|
23 |
-
The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `cwebp`, `
|
24 |
|
25 |
### The "Serving webp to browsers that supports it" part.
|
26 |
|
@@ -30,9 +26,13 @@ The plugin supports different ways of delivering webps to browsers that supports
|
|
30 |
2. By altering the HTML, replacing image tags with *picture* tags. Missing webps are auto generated upon visit.
|
31 |
3. By altering the HTML, replacing image URLs so all points to webp. The replacements only being made for browsers that supports webp. Again, missing webps are auto generated upon visit.
|
32 |
4. In combination with *Cache Enabler*, the same as above can be achieved, but with page caching.
|
33 |
-
5. You can also deliver webp to *all* browsers and add the [webpjs](http://webpjs.appspot.com) javascript, which provides webp support for browsers that doesn't support webp natively.
|
34 |
|
35 |
-
The plugin
|
|
|
|
|
|
|
|
|
36 |
|
37 |
### Benefits
|
38 |
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
|
@@ -153,67 +153,33 @@ Easy enough. Browsers looks at the *content type* header rather than the URL to
|
|
153 |
|
154 |
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
|
155 |
|
156 |
-
= I am on NGINX
|
157 |
-
The easy solution is simply to use the plugin in "CDN friendly" mode, do a bulk conversion (takes care of converting existing images) and activate the "Convert on upload" option (takes care of converting new images in the media library).
|
158 |
|
159 |
-
|
|
|
160 |
|
161 |
-
|
|
|
|
|
162 |
|
163 |
-
|
|
|
164 |
|
165 |
-
|
166 |
|
167 |
-
|
168 |
-
First step is to redirect images to the script. Second step is redirecting directly to existing webp images. Finally, as an optional third step, you can add extra rules for enabling the *Convert non-existing webp-files upon request* functionality
|
169 |
|
170 |
-
**
|
171 |
|
172 |
-
|
|
|
173 |
|
|
|
174 |
Insert the following in the `server` context of your configuration file (usually found in `/etc/nginx/sites-available`). "The `server` context" refers to the part of the configuration that starts with "server {" and ends with the matching "}".
|
175 |
|
176 |
`
|
177 |
-
|
178 |
-
|
179 |
-
if ($http_accept !~* "webp"){
|
180 |
-
break;
|
181 |
-
}
|
182 |
-
try_files
|
183 |
-
/nonexisting-because-try-files-needs-fallback
|
184 |
-
/wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content
|
185 |
-
;
|
186 |
-
}
|
187 |
-
`
|
188 |
-
*Beware when copy/pasting: You might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded*
|
189 |
-
|
190 |
-
If you have moved wp-content to a non-standard place, you must change accordingly. Especially note that you must also change the wp-content parameter to the script. It expects a relative path to wp-content (from document root) and is needed so the script can find the configuration file.
|
191 |
-
|
192 |
-
The `xsource` parameter helps the script finding the source file. It is only needed on some setups. You can try deleting it and see if it still works.
|
193 |
-
|
194 |
-
Beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g", so the first line becomes:
|
195 |
-
`
|
196 |
-
location ~* ^/?wp-content/.*\.jpe?g$ {
|
197 |
-
`
|
198 |
-
|
199 |
-
If you cannot get this to work then perhaps you need to add the following to your `mime.types` configuration file:
|
200 |
-
`
|
201 |
-
image/webp webp;
|
202 |
-
`
|
203 |
-
|
204 |
-
If you still cannot get it to work, you can instead try *method 2*
|
205 |
-
|
206 |
-
Note: There is no longer any reason to add "&$args" to the line begining with "/wp-content". It was there to enable debugging a single image by appending "?debug" to the url. I however removed that functionality from `webp-on-demand.php`.
|
207 |
-
|
208 |
-
**Step 2**: *Redirecting directly to existing webp images.*
|
209 |
-
|
210 |
-
Once you got this working, lets improve performance by redirecting directly to existing webp images. This step isn't necessary, as the script also does that - but invoking the php script takes more resources that the direct redirect. Also, a direct redirect will produce *ETag* response header, which is increases caching performance.
|
211 |
-
|
212 |
-
The rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you must configure *WebP Express* to store the converted files like that.
|
213 |
-
|
214 |
-
The following rules works both when WebP Express are configured to store images in the same folder as the originals ("mingled") and when or in a separate folder. Nginx will first see if there is a corresponding webp in the separate folder and then if there is a corresponding webp in the same folder and finally fall back to calling the conversion script.
|
215 |
-
|
216 |
-
`
|
217 |
location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
218 |
add_header Vary Accept;
|
219 |
expires 365d;
|
@@ -226,51 +192,54 @@ location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
|
226 |
/wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content
|
227 |
;
|
228 |
}
|
229 |
-
`
|
230 |
-
*Beware when copy/pasting: You might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded*
|
231 |
-
|
232 |
-
If you have configured WebP Express to store images in separate folder, you do not need the "$uri.webp" line. But it doesn't hurt to have it. But the reverse is not true. If configured to store images in the same folder ("mingled"), you still need the line that looks for a webp in the separate folder. The reason for this is that the "mingled" only applies to the images in the upload folder - other images - such as theme images are always stored in a separate folder.
|
233 |
-
|
234 |
-
Again, beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g".
|
235 |
-
|
236 |
-
Credits: This second step builds on [Eugene Lazutkins solution](http://www.lazutkin.com/blog/2014/02/23/serve-files-with-nginx-conditionally/).
|
237 |
-
|
238 |
-
**Step 3:**: *Caching*
|
239 |
-
In most cases you can and should allow images to be cached for a long period. If you do not want to do that, simply remove the "expires 365d;" line.
|
240 |
|
241 |
-
|
242 |
-
|
243 |
-
Simply add the following rules below the ones you added in step 2:
|
244 |
-
|
245 |
-
`
|
246 |
location ~* ^/?wp-content/.*\.(png|jpe?g)\.webp$ {
|
247 |
try_files
|
248 |
$uri
|
249 |
/wp-content/plugins/webp-express/wod/webp-realizer.php?wp-content=wp-content
|
250 |
;
|
251 |
}
|
|
|
252 |
`
|
253 |
|
254 |
-
|
|
|
255 |
|
|
|
256 |
|
257 |
-
|
258 |
|
259 |
-
|
260 |
|
261 |
-
|
262 |
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
}
|
267 |
-
}
|
268 |
-
`
|
269 |
|
270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
271 |
|
272 |
-
Make sure that WebP Express is configured with "Destination" set to "Mingled". And then insert the following in the `server` context (replacing the rules you inserted in step 1)
|
273 |
`
|
|
|
|
|
274 |
location ~* ^/wp-content/.*\.(png|jpe?g)$ {
|
275 |
add_header Vary Accept;
|
276 |
expires 365d;
|
@@ -293,22 +262,30 @@ if ($whattodo = AB) {
|
|
293 |
if ($whattodo = A) {
|
294 |
rewrite ^/wp-content/.*\.(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
|
295 |
}
|
296 |
-
|
297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
|
299 |
-
|
300 |
|
301 |
-
|
302 |
-
1. Set *Destination folder* to *mingled*
|
303 |
-
2. Set *File extension* to *Append ".webp"*
|
304 |
|
305 |
-
|
306 |
|
307 |
-
|
308 |
|
309 |
It is possible to put this stuff inside a `location` directive. However, having `if` directives inside `location` directives [is considered evil](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/). But it seems that in our case, it works. If you wish to do that, use the following rules instead:
|
310 |
|
311 |
`
|
|
|
|
|
312 |
location ~* ^/wp-content/.*\.(png|jpe?g)$ {
|
313 |
add_header Vary Accept;
|
314 |
expires 365d;
|
@@ -333,13 +310,24 @@ location ~* ^/wp-content/.*\.webp$ {
|
|
333 |
add_header Vary Accept;
|
334 |
}
|
335 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
336 |
`
|
337 |
-
*Beware when copy/pasting: You might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded*
|
338 |
|
339 |
Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
|
340 |
And here: https://github.com/rosell-dk/webp-express/issues/166
|
341 |
|
342 |
-
Here are rules if you need to replace the file extension with ".webp" rather than appending ".webp" to it: https://www.keycdn.com/support/optimus/configuration-to-deliver-webp
|
343 |
|
344 |
= I am on a Windows server =
|
345 |
Good news! It should work now, thanks to a guy that calls himself lwxbr. At least on XAMPP 7.3.1, Windows 10. https://github.com/rosell-dk/webp-express/pull/213.
|
@@ -520,7 +508,7 @@ If you do not want to use serve varied images:
|
|
520 |
- Open WebP Express options
|
521 |
- Switch to *CDN friendly* mode.
|
522 |
- Set *File extension* to "Set to .webp"
|
523 |
-
- Enable *Alter HTML* and select *Replace image URLs*. It is not absolutely
|
524 |
- If you enabled *Alter HTML*, also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*
|
525 |
- If you did not enable *Alter HTML*, enable *Convert non-existing webp-files upon request to original image*
|
526 |
|
@@ -612,6 +600,19 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
|
|
612 |
|
613 |
== Changelog ==
|
614 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
615 |
= 0.14.22 =
|
616 |
*(released: 4 aug 2019)*
|
617 |
|
@@ -636,7 +637,14 @@ Easy enough! - [Go here!](https://ko-fi.com/rosell). Or [here](https://buymeacof
|
|
636 |
* Fixed bug: Ewww api-key was forgot upon saving options
|
637 |
|
638 |
= 0.14.19 =
|
639 |
-
*(released: 28 jun 2019)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
640 |
|
641 |
* Removed a line that might course Sanity Check to fail ("path not within document root")
|
642 |
|
@@ -937,6 +945,9 @@ For older releases, check out changelog.txt
|
|
937 |
|
938 |
== Upgrade Notice ==
|
939 |
|
|
|
|
|
|
|
940 |
= 0.14.22 =
|
941 |
* A bundle of bug fixes and a security fix for Windows
|
942 |
|
4 |
Tags: webp, images, performance
|
5 |
Requires at least: 4.0
|
6 |
Tested up to: 5.2
|
7 |
+
Stable tag: 0.15.0
|
8 |
Requires PHP: 5.6
|
9 |
License: GPLv3
|
10 |
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
15 |
|
16 |
Almost 4 out of 5 mobile users use a browser that is able to display webp images. Yet, on most websites, they are served jpeg images, which are typically double the size of webp images for a given quality. What a waste of bandwidth! This plugin was created to help remedy that situation. With little effort, Wordpress admins can have their site serving autogenerated webp images to browsers that supports it, while still serving jpeg and png files to browsers that does not support webp.
|
17 |
|
|
|
|
|
|
|
|
|
18 |
### The image converter
|
19 |
+
The plugin uses the [WebP Convert](https://github.com/rosell-dk/webp-convert) library to convert images to webp. *WebP Convert* is able to convert images using multiple methods. There are the "local" conversion methods: `imagick`, `cwebp`, `vips`, `gd`. If none of these works on your host, there are the cloud alternatives: `ewww` (paid) or connecting to a Wordpress site where you got WebP Express installed and you enabled the "web service" functionality.
|
20 |
|
21 |
### The "Serving webp to browsers that supports it" part.
|
22 |
|
26 |
2. By altering the HTML, replacing image tags with *picture* tags. Missing webps are auto generated upon visit.
|
27 |
3. By altering the HTML, replacing image URLs so all points to webp. The replacements only being made for browsers that supports webp. Again, missing webps are auto generated upon visit.
|
28 |
4. In combination with *Cache Enabler*, the same as above can be achieved, but with page caching.
|
29 |
+
5. You can also deliver webp to *all* browsers and add the [webpjs](http://webpjs.appspot.com) javascript, which provides webp support for browsers that doesn't support webp natively. However, beware that the javascript doesn't support srcset attributes, which is why I haven't added that method to the plugin (yet).
|
30 |
|
31 |
+
The plugin implements the "WebP On Demand" solution described [here](https://github.com/rosell-dk/webp-convert/blob/master/docs/v2.0/webp-on-demand/webp-on-demand.md) and builds on a bunch of open source libraries (all maintained by me):
|
32 |
+
- [WebPConvert](https://github.com/rosell-dk/webp-convert): For converting images to webp
|
33 |
+
- [WebP Convert Cloud Service](https://github.com/rosell-dk/webp-convert-cloud-service): For the Web Service functionality
|
34 |
+
- [DOM Util for WebP](https://github.com/rosell-dk/dom-util-for-webp): For the Alter HTML functionality
|
35 |
+
- [Image MimeType Guesser](https://github.com/rosell-dk/image-mime-type-guesser): For detecting mime types of images.
|
36 |
|
37 |
### Benefits
|
38 |
- Much faster load time for images in browsers that supports webp. The converted images are typically *less than half the size* (for jpeg), while maintaining the same quality. Bear in mind that for most web sites, images are responsible for the largest part of the waiting time.
|
153 |
|
154 |
I am btw considering making an option to have the plugin redirect to the webp instead of serving immediately. That would remove the apparent mismatch between file extension and content type header. However, the cost of doing that will be an extra request for each image, which means extra time and worse performance. I believe you'd be ill advised to use that option, so I guess I will not implement it. But perhaps you have good reasons to use it? If you do, please let me know!
|
155 |
|
156 |
+
= I am on NGINX or OpenResty =
|
|
|
157 |
|
158 |
+
**The simple way (no redirecting rules)**
|
159 |
+
The easy solution is simply to use the plugin in "CDN friendly" mode, do a bulk conversion (takes care of converting existing images) and activate the "Convert on upload" option (takes care of converting new images in the media library).
|
160 |
|
161 |
+
*PRO*: Very easy to set up.
|
162 |
+
*CON*: Images in external CSS and images being dynamically added with javascript will not be served as webp.
|
163 |
+
*CON*: New new theme images will not be converted until you run a new Bulk conversion
|
164 |
|
165 |
+
**The advanced way (creating NGINX redirecting rules)**
|
166 |
+
Creating NGINX rules requires manually inserting redirection rules in the NGINX configuration file (nginx.conf or the configuration file for the site, found in `/etc/nginx/sites-available`). If you do not have access to do that, you will have to settle with the "simple way" described above.
|
167 |
|
168 |
+
There are two different approaches to achieve the redirections. The one that I recommend is based on a *try_files* directive. If that doesn't work for you, you can try the alternative rules that are based on the *rewrite* directive. The rules are described in the next couple of sections.
|
169 |
|
170 |
+
For multisite on NGINX, read [here](https://github.com/rosell-dk/webp-express/issues/8)
|
|
|
171 |
|
172 |
+
**Recommended rules (using "try_files")**
|
173 |
|
174 |
+
__Preparational step:__
|
175 |
+
The rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you must configure *WebP Express* to store the converted files like that by setting *General > File extension* to *Append ".webp"*
|
176 |
|
177 |
+
__The rules:__
|
178 |
Insert the following in the `server` context of your configuration file (usually found in `/etc/nginx/sites-available`). "The `server` context" refers to the part of the configuration that starts with "server {" and ends with the matching "}".
|
179 |
|
180 |
`
|
181 |
+
# WebP Express rules
|
182 |
+
# --------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
183 |
location ~* ^/?wp-content/.*\.(png|jpe?g)$ {
|
184 |
add_header Vary Accept;
|
185 |
expires 365d;
|
192 |
/wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content
|
193 |
;
|
194 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
|
196 |
+
# Route requests for non-existing webps to the converter
|
|
|
|
|
|
|
|
|
197 |
location ~* ^/?wp-content/.*\.(png|jpe?g)\.webp$ {
|
198 |
try_files
|
199 |
$uri
|
200 |
/wp-content/plugins/webp-express/wod/webp-realizer.php?wp-content=wp-content
|
201 |
;
|
202 |
}
|
203 |
+
# ------------------- (WebP Express rules ends here)
|
204 |
`
|
205 |
|
206 |
+
__BEWARE:__
|
207 |
+
- Beware that when copy/pasting you might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded (in the last line in the try_files block)
|
208 |
|
209 |
+
- Beware that the rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you __must__ configure *WebP Express* to store the converted files like that.
|
210 |
|
211 |
+
- Beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g".
|
212 |
|
213 |
+
- Beware that if you have moved wp-content to a non-standard place, you must change accordingly. Note that you must then also change the "wp-content" parameter to the script. It expects a relative path to wp-content (from document root) and is needed so the script can find the configuration file.
|
214 |
|
215 |
+
- I have put in an expires statement for caching. You might want to modify or disable that.
|
216 |
|
217 |
+
- The rules contains all redirections (as if you enabled all three redirection options in settings). If you do not wish to redirect to converter, remove the last line in the try_files block. If you do not wish to create webp files upon request, remove the last location block.
|
218 |
+
|
219 |
+
- If you have configured WebP Express to store images in separate folder, you do not need the "$uri.webp" line in the first "try_files" block. But it doesn't hurt to have it. And beware that the reverse is not true. If configured to store images in the same folder ("mingled"), you still need the line that looks for a webp in the separate folder. The reason for this is that the "mingled" only applies to the images in the upload folder - other images - such as theme images are always stored in a separate folder.
|
|
|
|
|
|
|
220 |
|
221 |
+
If you cannot get this to work then perhaps you need to add the following to your *mime.types* configuration file:
|
222 |
+
`image/webp webp;`
|
223 |
+
|
224 |
+
If you still cannot get it to work, you can instead try the alternative rules below.
|
225 |
+
|
226 |
+
Credits: These rules are builds upon [Eugene Lazutkins solution](http://www.lazutkin.com/blog/2014/02/23/serve-files-with-nginx-conditionally/).
|
227 |
+
|
228 |
+
**Alternative rules (using "rewrite")**
|
229 |
+
|
230 |
+
In case the recommended rules does not work for you, you can try these alternative rules.
|
231 |
+
|
232 |
+
The reason I recommend the *try_files* approach above over these alternative rules is that it is a bit simpler and it is supposed to perform marginally better. These alternative rules are in no way inferior to the other. Choose whatever works!
|
233 |
+
|
234 |
+
__Preparational step:__
|
235 |
+
The rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you must configure *WebP Express* to store the converted files like that by setting *General > File extension* to *Append ".webp"*. Also make sure that WebP Express is configured with "Destination" set to "Mingled".
|
236 |
+
|
237 |
+
__The rules:__
|
238 |
+
Insert the following in the `server` context of your configuration file (usually found in `/etc/nginx/sites-available`). "The `server` context" refers to the part of the configuration that starts with "server {" and ends with the matching "}".
|
239 |
|
|
|
240 |
`
|
241 |
+
# WebP Express rules
|
242 |
+
# --------------------
|
243 |
location ~* ^/wp-content/.*\.(png|jpe?g)$ {
|
244 |
add_header Vary Accept;
|
245 |
expires 365d;
|
262 |
if ($whattodo = A) {
|
263 |
rewrite ^/wp-content/.*\.(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
|
264 |
}
|
265 |
+
# ------------------- (WebP Express rules ends here)
|
266 |
+
```
|
267 |
+
|
268 |
+
__BEWARE:__
|
269 |
+
|
270 |
+
- Beware that when copy/pasting you might get html-encoded characters. Verify that the ampersand before "wp-content" isn't encoded (in the last line in the try_files block)
|
271 |
+
|
272 |
+
- Beware that the rules looks for existing webp files by appending ".webp" to the URL. So for this to work, you __must__ configure *WebP Express* to store the converted files like that.
|
273 |
+
|
274 |
+
- Beware that if you haven't enabled *png* conversion, you should replace "(png|jpe?g)" with "jpe?g".
|
275 |
|
276 |
+
- Beware that if you have moved wp-content to a non-standard place, you must change accordingly. Note that you must then also change the "wp-content" parameter to the script. It expects a relative path to wp-content (from document root) and is needed so the script can find the configuration file.
|
277 |
|
278 |
+
- I have put in an expires statement for caching. You might want to modify or disable that.
|
|
|
|
|
279 |
|
280 |
+
- I have not set any expire on the webp-on-demand.php request. This is not needed, as the script sets this according to what you set up in WebP Express settings. Also, trying to do it would require a new location block matching webp-on-demand.php, but that would override the location block handling php files, and thus break the functionality.
|
281 |
|
282 |
+
- There is no longer any reason to add "&$args" to the line begining with "/wp-content". It was there to enable debugging a single image by appending "?debug" to the url. I however removed that functionality from `webp-on-demand.php`.
|
283 |
|
284 |
It is possible to put this stuff inside a `location` directive. However, having `if` directives inside `location` directives [is considered evil](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/). But it seems that in our case, it works. If you wish to do that, use the following rules instead:
|
285 |
|
286 |
`
|
287 |
+
# WebP Express rules
|
288 |
+
# --------------------
|
289 |
location ~* ^/wp-content/.*\.(png|jpe?g)$ {
|
290 |
add_header Vary Accept;
|
291 |
expires 365d;
|
310 |
add_header Vary Accept;
|
311 |
}
|
312 |
}
|
313 |
+
# ------------------- (WebP Express rules ends here)
|
314 |
+
`
|
315 |
+
|
316 |
+
PS: In case you only want to redirect images to the script (and not to existing), the rules becomes much simpler:
|
317 |
+
|
318 |
+
`
|
319 |
+
# WebP Express rules
|
320 |
+
# --------------------
|
321 |
+
if ($http_accept ~* "webp"){
|
322 |
+
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?xsource=x$request_filename&wp-content=wp-content break;
|
323 |
+
}
|
324 |
+
# ------------------- (WebP Express rules ends here)
|
325 |
`
|
|
|
326 |
|
327 |
Discussion on this topic [here](https://wordpress.org/support/topic/nginx-rewrite-rules-4/)
|
328 |
And here: https://github.com/rosell-dk/webp-express/issues/166
|
329 |
|
330 |
+
Here are rules if you need to *replace* the file extension with ".webp" rather than appending ".webp" to it: https://www.keycdn.com/support/optimus/configuration-to-deliver-webp
|
331 |
|
332 |
= I am on a Windows server =
|
333 |
Good news! It should work now, thanks to a guy that calls himself lwxbr. At least on XAMPP 7.3.1, Windows 10. https://github.com/rosell-dk/webp-express/pull/213.
|
508 |
- Open WebP Express options
|
509 |
- Switch to *CDN friendly* mode.
|
510 |
- Set *File extension* to "Set to .webp"
|
511 |
+
- Enable *Alter HTML* and select *Replace image URLs*. It is not absolutely necessary, as Cache Enabler also alters HTML - but there are several reasons to do it. Firstly, *Cache Enabler* doesn't get as many URLs replaced as we do. WebP Express for example also replaces background urls in inline styles. Secondly, *Cache enabler* has [problems in edge cases](https://regexr.com/46isf). Thirdly, WebP Express can be configured to alter HTML to point to corresponding webp images, *before they even exists* which can be used in conjunction with the the *Convert non-existing webp-files upon request* option. And this is smart, because then you don't have trouble with *Cache Enabler* caching HTML which references the original images due to that some images hasn't been converted yet.
|
512 |
- If you enabled *Alter HTML*, also enable *Reference webps that hasn't been converted yet* and *Convert non-existing webp-files upon request*
|
513 |
- If you did not enable *Alter HTML*, enable *Convert non-existing webp-files upon request to original image*
|
514 |
|
600 |
|
601 |
== Changelog ==
|
602 |
|
603 |
+
= 0.15.0 =
|
604 |
+
*(released: 17 sep 2019)*
|
605 |
+
|
606 |
+
* Provided test-buttons for checking if the redirects works.
|
607 |
+
* You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
|
608 |
+
* You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
|
609 |
+
* WebP Express can now handle when wp-content is symlinked.
|
610 |
+
* The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
|
611 |
+
* Added option to convert only PNG files
|
612 |
+
* And a couple of bugfixes.
|
613 |
+
|
614 |
+
For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/22?closed=1
|
615 |
+
|
616 |
= 0.14.22 =
|
617 |
*(released: 4 aug 2019)*
|
618 |
|
637 |
* Fixed bug: Ewww api-key was forgot upon saving options
|
638 |
|
639 |
= 0.14.19 =
|
640 |
+
*(released: 28 jun 2019)** Provided test-buttons for checking if the redirects works.
|
641 |
+
* You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
|
642 |
+
* You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
|
643 |
+
* WebP Express can now handle when wp-content is symlinked.
|
644 |
+
* The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
|
645 |
+
* And a couple of bugfixes.
|
646 |
+
|
647 |
+
For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/22?closed=1
|
648 |
|
649 |
* Removed a line that might course Sanity Check to fail ("path not within document root")
|
650 |
|
945 |
|
946 |
== Upgrade Notice ==
|
947 |
|
948 |
+
= 0.15.0 =
|
949 |
+
* New "Scope" and "destination structure" options, nice test buttons and a lot of work behind the surface
|
950 |
+
|
951 |
= 0.14.22 =
|
952 |
* A bundle of bug fixes and a security fix for Windows
|
953 |
|
changelog.txt
CHANGED
@@ -1,3 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
= 0.13.0 =
|
2 |
*(released: 21 mar 2019)*
|
3 |
* Bulk Conversion
|
1 |
+
= 0.15.0 =
|
2 |
+
*(released: 17 sep 2019)*
|
3 |
+
|
4 |
+
* Provided test-buttons for checking if the redirects works.
|
5 |
+
* You can now choose which folders WebP Express is active in. Ie "Uploads and Themes".
|
6 |
+
* You can now choose an alternative file structure for the webps which does not rely on DOCUMENT_ROOT being available.
|
7 |
+
* WebP Express can now handle when wp-content is symlinked.
|
8 |
+
* The .htaccess rules are now divided across folders. Some rules are needed where the source files are located, some where the webp files are located.
|
9 |
+
* Added option to convert only PNG files
|
10 |
+
* And a couple of bugfixes.
|
11 |
+
|
12 |
+
For more info, see the closed issues on the 0.15.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/22?closed=1
|
13 |
+
|
14 |
+
= 0.14.22 =
|
15 |
+
*(released: 4 aug 2019)*
|
16 |
+
|
17 |
+
* Fixed bug in Nginx rules in the FAQ (they did not take into account that the webp images outside upload folder are never stored "mingled")
|
18 |
+
* Fixed bug: The extension setting was not respected - it was always appending ".webp", never setting. Thanks to Florian from Germany and Derrick Hammer from USA for reporting.
|
19 |
+
* Fixed bug: It turns out that Imagick gives up quality detection for some images and returns 0. This resulted in very poor quality webps when the quality was set to same as jpeg. The 0 is now treated as a failure and the max-quality will be used for such images. Thanks to @sanjayojha303 from India for reporting that there were quality problems on some images.
|
20 |
+
* Fixed bug-like behavior: The conversion scripts no longer requires that the respective setting is on for Nginx. Thanks to Mike from Russia for reporting this.
|
21 |
+
* Fixed bug: The error handler in webp-convert for handling warnings could in some cases result in endless recursion. For some the result was that they could no longer upload images. Thanks to Tobias Keller from Germany for reporting this bug.
|
22 |
+
* Fixed minor bug: Attempt to call private method in a rare scenario (when accessing one of the php scripts in the "wod" folder directly - which is not allowed). Thanks to Giacomo Lawrance from the U.K. for providing input that led to this discovery.
|
23 |
+
* Fixed minor bug: It was not tested whether a corresponding webp existed before trying to deleting it when an image was deleted. This produced warnings in debug.log.
|
24 |
+
* Security related: Added sanitizing of paths to avoid false positives on coderisk.com (there where no risk because already test the paths for sanity - but this is not detected by coderisk, as the variable is not modified). This was simply done in order get rid of the warnings at coderisk.
|
25 |
+
* Security fix: Paths were not sanitized on Windows.
|
26 |
+
|
27 |
+
= 0.14.21 =
|
28 |
+
*(released: 30 jun 2019)*
|
29 |
+
|
30 |
+
* Hopefully fixed WebP Express Error: "png" option is Object
|
31 |
+
|
32 |
+
= 0.14.20 =
|
33 |
+
*(released: 29 jun 2019)*
|
34 |
+
|
35 |
+
* Fixed bug: Ewww api-key was forgot upon saving options
|
36 |
+
|
37 |
+
= 0.14.19 =
|
38 |
+
*(released: 28 jun 2019)*
|
39 |
+
|
40 |
+
* Removed a line that might course Sanity Check to fail ("path not within document root")
|
41 |
+
|
42 |
+
= 0.14.18 =
|
43 |
+
*(released: 28 jun 2019)*
|
44 |
+
|
45 |
+
* Fixed Sanity Error: Path is outside allowed path on systems using symlinked folders
|
46 |
+
* Updated cache breaking token for javascript in order for the last fix for changing password with Remote WebP Express to take effect
|
47 |
+
* Fixed undefined variable error in image_make_intermediate_size hook, which prevented webps thumbnails to be generated upon upload
|
48 |
+
* Minor bug fix in cwebp converter (updated to webp-convert v.2.1.4)
|
49 |
+
|
50 |
+
= 0.14.17 =
|
51 |
+
*(released: 28 jun 2019)*
|
52 |
+
|
53 |
+
* Relaxed abspath sanity check on Windows
|
54 |
+
* Fixed updating password for Remote WebP Express
|
55 |
+
|
56 |
+
= 0.14.16 =
|
57 |
+
*(released: 26 jun 2019)*
|
58 |
+
|
59 |
+
* Fixed conversion errors using Bulk convert or Test convert on systems with symlinked folders
|
60 |
+
|
61 |
+
= 0.14.15 =
|
62 |
+
*(released: 26 jun 2019)*
|
63 |
+
|
64 |
+
* Fixed errors with "redirect to conversion script" on systems with symlinked folders
|
65 |
+
* Fixed errors with "redirect to conversion script" on systems where the filename cannot be passed through an environment variable
|
66 |
+
|
67 |
+
= 0.14.14 =
|
68 |
+
*(released: 26 jun 2019)*
|
69 |
+
|
70 |
+
* Fixed errors on systems with symlinked folders
|
71 |
+
|
72 |
+
= 0.14.13 =
|
73 |
+
*(released: 26 jun 2019)*
|
74 |
+
|
75 |
+
* Fixed errors in conversion scripts
|
76 |
+
|
77 |
+
= 0.14.12 =
|
78 |
+
*(released: 26 jun 2019)*
|
79 |
+
|
80 |
+
* Fixed critical bug
|
81 |
+
|
82 |
+
= 0.14.11 =
|
83 |
+
*(released: 24 jun 2019)*
|
84 |
+
|
85 |
+
The following security fixes has been applied in 0.14.0 - 0.14.11:
|
86 |
+
It is urged that you upgrade all of you WebP Express installations!
|
87 |
+
|
88 |
+
– Security fix: Closed a security hole that could be used to view the content of any file on the server (provided that the full path is known or guessed). This is a very serious flaw, which unfortunately has been around for quite a while.
|
89 |
+
– Security fix: Added capability checks to options page.
|
90 |
+
– Security fix: Sanitized user input.
|
91 |
+
– Security fix: Added checks for file paths and directories.
|
92 |
+
– Security fix: Nonces and capability checks for AJAX calls.
|
93 |
+
|
94 |
+
= 0.14.10 =
|
95 |
+
*(released: 24 jun 2019)*
|
96 |
+
|
97 |
+
* Security related
|
98 |
+
|
99 |
+
= 0.14.9 =
|
100 |
+
*(released: 22 jun 2019)*
|
101 |
+
|
102 |
+
* Security related
|
103 |
+
|
104 |
+
= 0.14.8 =
|
105 |
+
*(released: 21 jun 2019)*
|
106 |
+
|
107 |
+
* Security related
|
108 |
+
|
109 |
+
= 0.14.7 =
|
110 |
+
*(released: 20 jun 2019)*
|
111 |
+
|
112 |
+
* Security related: Removed unneccesary files from webp-convert library
|
113 |
+
|
114 |
+
= 0.14.6 =
|
115 |
+
*(released: 20 jun 2019)*
|
116 |
+
|
117 |
+
* Security related
|
118 |
+
|
119 |
+
= 0.14.5 =
|
120 |
+
*(released: 20 jun 2019)*
|
121 |
+
|
122 |
+
* Security related
|
123 |
+
|
124 |
+
= 0.14.4 =
|
125 |
+
*(released: 18 jun 2019)*
|
126 |
+
|
127 |
+
* Now bundles with multiple cwebp binaries for linux for systems where 1.0.2 fails.
|
128 |
+
|
129 |
+
= 0.14.3 =
|
130 |
+
*(released: 18 jun 2019)*
|
131 |
+
|
132 |
+
* Fixed filename of supplied cwebp for linux (bug was introduced in 0.14.2)
|
133 |
+
|
134 |
+
= 0.14.2 =
|
135 |
+
*(released: 17 jun 2019)*
|
136 |
+
|
137 |
+
* Fixed problem with older versions of cwebp
|
138 |
+
* Fixed that images was not deleted
|
139 |
+
* Fixed cache problem on options page on systems that disables cache busting (it resulted in "SyntaxError: JSON.parse")
|
140 |
+
|
141 |
+
= 0.14.1 =
|
142 |
+
*(released: 15 jun 2019)*
|
143 |
+
|
144 |
+
* Security related
|
145 |
+
|
146 |
+
= 0.14.0 =
|
147 |
+
*(released: 15 jun 2019)*
|
148 |
+
|
149 |
+
* Security fix: Closed a security hole that could be used to view the content of any file on the server (provided that the full path is known or guessed). This is a very serious flaw, which has been around for quite a while. I urge you to upgrade to 0.14.0.
|
150 |
+
* Added new "encoding" option, which can be set to auto. This can in some cases dramatically reduce the size of the webp. It is supported by all converters except ewww and gd.
|
151 |
+
* Added new "near-lossless" option (only for cwebp and vips). Using this is a good idea for reducing size of lossless webps with an acceptable loss of quality
|
152 |
+
* Added new "alpha-quality" option (all converters, except ewww and gd). Using this is a good idea when images with transparency are converted to lossy webp - it has the potential to reduce the size up to 50% (depending on the source material) while keeping an acceptable level of quality
|
153 |
+
* Added new conversion methods: Vips and GraphicsMagick
|
154 |
+
* Imagick conversion method now supports webp options (finally cracked it!)
|
155 |
+
* Using MimeType detection instead of relying on file extensions
|
156 |
+
* In "test" converter you now change options and also test PNG files
|
157 |
+
* Added conversion logs
|
158 |
+
* PNGs are now enabled by default (with the new conversion features especially PNGs are compressed much better)
|
159 |
+
|
160 |
+
For more info, see the closed issues on the 0.14.0 milestone on the github repository: https://github.com/rosell-dk/webp-express/milestone/9?closed=1
|
161 |
+
|
162 |
= 0.13.0 =
|
163 |
*(released: 21 mar 2019)*
|
164 |
* Bulk Conversion
|
composer.lock
CHANGED
@@ -118,16 +118,16 @@
|
|
118 |
},
|
119 |
{
|
120 |
"name": "rosell-dk/webp-convert",
|
121 |
-
"version": "2.1.
|
122 |
"source": {
|
123 |
"type": "git",
|
124 |
"url": "https://github.com/rosell-dk/webp-convert.git",
|
125 |
-
"reference": "
|
126 |
},
|
127 |
"dist": {
|
128 |
"type": "zip",
|
129 |
-
"url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/
|
130 |
-
"reference": "
|
131 |
"shasum": ""
|
132 |
},
|
133 |
"require": {
|
@@ -190,7 +190,7 @@
|
|
190 |
"png",
|
191 |
"png2webp"
|
192 |
],
|
193 |
-
"time": "2019-08-
|
194 |
},
|
195 |
{
|
196 |
"name": "rosell-dk/webp-convert-cloud-service",
|
118 |
},
|
119 |
{
|
120 |
"name": "rosell-dk/webp-convert",
|
121 |
+
"version": "2.1.6",
|
122 |
"source": {
|
123 |
"type": "git",
|
124 |
"url": "https://github.com/rosell-dk/webp-convert.git",
|
125 |
+
"reference": "5cb744d2786468fb51883d1f3a90562166df3320"
|
126 |
},
|
127 |
"dist": {
|
128 |
"type": "zip",
|
129 |
+
"url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/5cb744d2786468fb51883d1f3a90562166df3320",
|
130 |
+
"reference": "5cb744d2786468fb51883d1f3a90562166df3320",
|
131 |
"shasum": ""
|
132 |
},
|
133 |
"require": {
|
190 |
"png",
|
191 |
"png2webp"
|
192 |
],
|
193 |
+
"time": "2019-08-10T22:04:08+00:00"
|
194 |
},
|
195 |
{
|
196 |
"name": "rosell-dk/webp-convert-cloud-service",
|
docs/development.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
## Updating the vendor dir:
|
2 |
|
3 |
-
1. Run `composer update` in the root.
|
4 |
2. Run `composer dump-autoload -o`
|
5 |
(for some reason, `vendor/composer/autoload_classmap.php` looses all its mappings on composer update). It also looses them on a `composer dump-autoload` (without the -o option).
|
6 |
It actually seems that the mappings are not needed. It seems to work fine when I alter autoload_real to not use the static loader. But well, I'm reluctant to change anything that works.
|
@@ -11,21 +11,8 @@ It actually seems that the mappings are not needed. It seems to work fine when I
|
|
11 |
- cd into the folder
|
12 |
|
13 |
```
|
14 |
-
rm -r tests
|
15 |
-
|
16 |
-
rm -r vendor/rosell-dk/webp-convert/build-scripts
|
17 |
-
rm -r vendor/rosell-dk/webp-convert/tests
|
18 |
-
rm -r vendor/rosell-dk/webp-convert/build-tests-webp-convert
|
19 |
-
rm -r vendor/rosell-dk/webp-convert/build-tests-wod
|
20 |
-
rm -r vendor/rosell-dk/webp-convert/src-build
|
21 |
rm -r vendor/rosell-dk/webp-convert/docs
|
22 |
-
rm vendor/rosell-dk/webp-convert/*.
|
23 |
-
rm -r vendor/rosell-dk/webp-convert/src//Helpers/*.txt
|
24 |
-
rm vendor/rosell-dk/webp-convert/.gitignore
|
25 |
-
|
26 |
-
rm -r vendor/rosell-dk/webp-convert-cloud-service/tests
|
27 |
-
rm -r vendor/rosell-dk/webp-convert-cloud-service/docs
|
28 |
-
|
29 |
rm vendor/rosell-dk/dom-util-for-webp/phpstan.neon
|
30 |
|
31 |
```
|
1 |
## Updating the vendor dir:
|
2 |
|
3 |
+
1. Run `composer update` in the root (plugin root).
|
4 |
2. Run `composer dump-autoload -o`
|
5 |
(for some reason, `vendor/composer/autoload_classmap.php` looses all its mappings on composer update). It also looses them on a `composer dump-autoload` (without the -o option).
|
6 |
It actually seems that the mappings are not needed. It seems to work fine when I alter autoload_real to not use the static loader. But well, I'm reluctant to change anything that works.
|
11 |
- cd into the folder
|
12 |
|
13 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
rm -r vendor/rosell-dk/webp-convert/docs
|
15 |
+
rm -r vendor/rosell-dk/webp-convert/src/Helpers/*.txt
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
rm vendor/rosell-dk/dom-util-for-webp/phpstan.neon
|
17 |
|
18 |
```
|
lib/classes/AdminInit.php
CHANGED
@@ -30,7 +30,7 @@ class AdminInit
|
|
30 |
public static function runMigrationIfNeeded()
|
31 |
{
|
32 |
// When an update requires a migration, the number should be increased
|
33 |
-
define('WEBPEXPRESS_MIGRATION_VERSION', '
|
34 |
|
35 |
if (WEBPEXPRESS_MIGRATION_VERSION != Option::getOption('webp-express-migration-version', 0)) {
|
36 |
// run migration logic
|
@@ -38,7 +38,7 @@ class AdminInit
|
|
38 |
}
|
39 |
|
40 |
// uncomment next line to test-run a migration
|
41 |
-
//include WEBPEXPRESS_PLUGIN_DIR . '/lib/migrate/
|
42 |
}
|
43 |
|
44 |
public static function adminInitHandler()
|
@@ -62,6 +62,7 @@ class AdminInit
|
|
62 |
add_action('wp_ajax_webpexpress_view_log', array('\WebPExpress\ConvertLog', 'processAjaxViewLog'));
|
63 |
add_action('wp_ajax_webpexpress_purge_cache', array('\WebPExpress\CachePurge', 'processAjaxPurgeCache'));
|
64 |
add_action('wp_ajax_webpexpress_dismiss_message', array('\WebPExpress\DismissableMessages', 'processAjaxDismissMessage'));
|
|
|
65 |
|
66 |
|
67 |
// Add settings link on the plugins page
|
30 |
public static function runMigrationIfNeeded()
|
31 |
{
|
32 |
// When an update requires a migration, the number should be increased
|
33 |
+
define('WEBPEXPRESS_MIGRATION_VERSION', '11');
|
34 |
|
35 |
if (WEBPEXPRESS_MIGRATION_VERSION != Option::getOption('webp-express-migration-version', 0)) {
|
36 |
// run migration logic
|
38 |
}
|
39 |
|
40 |
// uncomment next line to test-run a migration
|
41 |
+
//include WEBPEXPRESS_PLUGIN_DIR . '/lib/migrate/migrate11.php';
|
42 |
}
|
43 |
|
44 |
public static function adminInitHandler()
|
62 |
add_action('wp_ajax_webpexpress_view_log', array('\WebPExpress\ConvertLog', 'processAjaxViewLog'));
|
63 |
add_action('wp_ajax_webpexpress_purge_cache', array('\WebPExpress\CachePurge', 'processAjaxPurgeCache'));
|
64 |
add_action('wp_ajax_webpexpress_dismiss_message', array('\WebPExpress\DismissableMessages', 'processAjaxDismissMessage'));
|
65 |
+
add_action('wp_ajax_webpexpress_self_test', array('\WebPExpress\SelfTest', 'processAjax'));
|
66 |
|
67 |
|
68 |
// Add settings link on the plugins page
|
lib/classes/AlterHtmlHelper.php
CHANGED
@@ -81,7 +81,7 @@ class AlterHtmlHelper
|
|
81 |
|
82 |
/**
|
83 |
* Looks if $imageUrl is rooted in $baseUrl and if the file is there
|
84 |
-
*
|
85 |
*
|
86 |
* @param $imageUrl (ie http://example.com/wp-content/image.jpg)
|
87 |
* @param $baseUrl (ie http://example.com/wp-content)
|
@@ -109,7 +109,7 @@ class AlterHtmlHelper
|
|
109 |
|
110 |
}
|
111 |
|
112 |
-
|
113 |
public static function isSourceInUpload($src)
|
114 |
{
|
115 |
/* $src is ie http://we0/wp-content-moved/themes/twentyseventeen/assets/images/header.jpg */
|
@@ -135,13 +135,14 @@ class AlterHtmlHelper
|
|
135 |
*
|
136 |
* returns false
|
137 |
* - if no source file found in that base
|
138 |
-
* - or webp file isn't there and the `only-for-webps-that-exists` option is set
|
139 |
*
|
140 |
-
* @param
|
141 |
-
* @param
|
142 |
-
* @param
|
|
|
143 |
*/
|
144 |
-
|
145 |
{
|
146 |
//error_log('getWebPUrlInBase:' . $sourceUrl . ':' . $baseUrl . ':' . $baseDir);
|
147 |
|
@@ -151,40 +152,44 @@ class AlterHtmlHelper
|
|
151 |
return false;
|
152 |
}
|
153 |
|
154 |
-
// Calculate file path to
|
155 |
$srcPathAbs = $baseDir . $srcPathRel;
|
156 |
|
157 |
-
// Check that
|
158 |
if (!@file_exists($srcPathAbs)) {
|
159 |
return false;
|
160 |
}
|
161 |
|
|
|
|
|
162 |
|
163 |
-
//
|
164 |
-
// -------------------------------------
|
165 |
-
$inUpload = self::isSourceInUpload($sourceUrl);
|
166 |
|
167 |
-
if ((self::$options['
|
168 |
-
|
169 |
-
if (self::$options['destination-extension'] == 'append') {
|
170 |
-
$destPathAbs = $srcPathAbs . '.webp';
|
171 |
-
$destUrl = $sourceUrl . '.webp';
|
172 |
-
} else {
|
173 |
-
$destPathAbs = preg_replace('/\\.(png|jpe?g)$/', '', $srcPathAbs) . '.webp';
|
174 |
-
$destUrl = preg_replace('/\\.(png|jpe?g)$/', '', $sourceUrl) . '.webp';
|
175 |
-
}
|
176 |
-
} else {
|
177 |
-
// separate (images that are not in upload are always put in separate)
|
178 |
-
|
179 |
-
$relPathFromDocRoot = '/webp-express/webp-images/doc-root/';
|
180 |
-
$relPathFromDocRoot .= PathHelper::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $baseDir) . $srcPathRel;
|
181 |
-
|
182 |
-
list ($contentDirAbs, $contentUrl) = self::$options['bases']['content'];
|
183 |
-
|
184 |
-
$destPathAbs = $contentDirAbs . $relPathFromDocRoot . '.webp';
|
185 |
-
$destUrl = $contentUrl . $relPathFromDocRoot . '.webp';
|
186 |
}
|
187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
$webpMustExist = self::$options['only-for-webps-that-exists'];
|
189 |
if ($webpMustExist && (!@file_exists($destPathAbs))) {
|
190 |
return false;
|
@@ -243,7 +248,7 @@ class AlterHtmlHelper
|
|
243 |
$baseDir = Paths::getUploadDirAbs();
|
244 |
}
|
245 |
|
246 |
-
$result = self::getWebPUrlInBase($sourceUrl, $baseUrl, $baseDir);
|
247 |
if ($result !== false) {
|
248 |
return $result;
|
249 |
}
|
81 |
|
82 |
/**
|
83 |
* Looks if $imageUrl is rooted in $baseUrl and if the file is there
|
84 |
+
* PS: NOT USED ANYMORE!
|
85 |
*
|
86 |
* @param $imageUrl (ie http://example.com/wp-content/image.jpg)
|
87 |
* @param $baseUrl (ie http://example.com/wp-content)
|
109 |
|
110 |
}
|
111 |
|
112 |
+
// NOT USED ANYMORE
|
113 |
public static function isSourceInUpload($src)
|
114 |
{
|
115 |
/* $src is ie http://we0/wp-content-moved/themes/twentyseventeen/assets/images/header.jpg */
|
135 |
*
|
136 |
* returns false
|
137 |
* - if no source file found in that base
|
138 |
+
* - or source file is found but webp file isn't there and the `only-for-webps-that-exists` option is set
|
139 |
*
|
140 |
+
* @param string $sourceUrl Url of source image (ie http://example.com/wp-content/image.jpg)
|
141 |
+
* @param string $rootId Id (created in Config::updateAutoloadedOptions). Ie "uploads", "content" or any image root id
|
142 |
+
* @param string $baseUrl Base url of source image (ie http://example.com/wp-content)
|
143 |
+
* @param string $baseDir Base dir of source image (ie /var/www/example.com/wp-content)
|
144 |
*/
|
145 |
+
public static function getWebPUrlInBase($sourceUrl, $rootId, $baseUrl, $baseDir)
|
146 |
{
|
147 |
//error_log('getWebPUrlInBase:' . $sourceUrl . ':' . $baseUrl . ':' . $baseDir);
|
148 |
|
152 |
return false;
|
153 |
}
|
154 |
|
155 |
+
// Calculate file path to source
|
156 |
$srcPathAbs = $baseDir . $srcPathRel;
|
157 |
|
158 |
+
// Check that source file exists
|
159 |
if (!@file_exists($srcPathAbs)) {
|
160 |
return false;
|
161 |
}
|
162 |
|
163 |
+
// Calculate destination of webp (both path and url)
|
164 |
+
// ----------------------------------------
|
165 |
|
166 |
+
// We are calculating: $destPathAbs and $destUrl.
|
|
|
|
|
167 |
|
168 |
+
if (!isset(self::$options['bases'][$rootId])) {
|
169 |
+
return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
}
|
171 |
|
172 |
+
$destinationRoot = Paths::destinationRoot(
|
173 |
+
$rootId,
|
174 |
+
self::$options['destination-folder'],
|
175 |
+
self::$options['destination-structure']
|
176 |
+
);
|
177 |
+
|
178 |
+
$relPathFromImageRootToSource = PathHelper::getRelDir(
|
179 |
+
realpath(Paths::getAbsDirById($rootId)),
|
180 |
+
realpath($srcPathAbs)
|
181 |
+
);
|
182 |
+
$relPathFromImageRootToDest = ConvertHelperIndependent::appendOrSetExtension(
|
183 |
+
$relPathFromImageRootToSource,
|
184 |
+
self::$options['destination-folder'],
|
185 |
+
self::$options['destination-extension'],
|
186 |
+
($rootId == 'uploads')
|
187 |
+
);
|
188 |
+
$result['destination-url'] = $destinationRoot['url'] . '/' . $relPathFromImageRootToDest;
|
189 |
+
|
190 |
+
$destPathAbs = $destinationRoot['abs-path'] . '/' . $relPathFromImageRootToDest;
|
191 |
+
$destUrl = $destinationRoot['url'] . '/' . $relPathFromImageRootToDest;
|
192 |
+
|
193 |
$webpMustExist = self::$options['only-for-webps-that-exists'];
|
194 |
if ($webpMustExist && (!@file_exists($destPathAbs))) {
|
195 |
return false;
|
248 |
$baseDir = Paths::getUploadDirAbs();
|
249 |
}
|
250 |
|
251 |
+
$result = self::getWebPUrlInBase($sourceUrl, $id, $baseUrl, $baseDir);
|
252 |
if ($result !== false) {
|
253 |
return $result;
|
254 |
}
|
lib/classes/BulkConvert.php
CHANGED
@@ -2,26 +2,11 @@
|
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
-
use \WebPExpress\ConvertHelperIndependent;
|
6 |
-
use \WebPExpress\Paths;
|
7 |
-
use \WebPExpress\PathHelper;
|
8 |
-
|
9 |
class BulkConvert
|
10 |
{
|
11 |
|
12 |
-
public static function getUploadFolder($destinationFolder)
|
13 |
-
{
|
14 |
-
switch ($destinationFolder) {
|
15 |
-
case 'mingled':
|
16 |
-
return Paths::getUploadDirAbs();
|
17 |
-
case 'separate':
|
18 |
-
return Paths::getCacheDirAbs() . '/doc-root/' . Paths::getUploadDirRel();
|
19 |
-
}
|
20 |
-
}
|
21 |
-
|
22 |
public static function getList($config)
|
23 |
{
|
24 |
-
//$cacheDir = self::getUploadFolder($config['destination-folder']);
|
25 |
|
26 |
|
27 |
/*
|
@@ -34,11 +19,12 @@ class BulkConvert
|
|
34 |
|
35 |
$listOptions = [
|
36 |
//'root' => Paths::getUploadDirAbs(),
|
37 |
-
//'cache-root' => self::getUploadFolder($config['destination-folder']),
|
38 |
'ext' => $config['destination-extension'],
|
39 |
'destination-folder' => $config['destination-folder'], /* hm, "destination-folder" is a bad name... */
|
40 |
'webExpressContentDirAbs' => Paths::getWebPExpressContentDirAbs(),
|
41 |
'uploadDirAbs' => Paths::getUploadDirAbs(),
|
|
|
|
|
42 |
'filter' => [
|
43 |
'only-converted' => false,
|
44 |
'only-unconverted' => true,
|
@@ -46,13 +32,42 @@ class BulkConvert
|
|
46 |
]
|
47 |
];
|
48 |
|
|
|
|
|
|
|
|
|
49 |
$groups = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
|
|
|
56 |
if (Paths::isUploadDirMovedOutOfWPContentDir()) {
|
57 |
$groups[] = [
|
58 |
'groupName' => 'uploads',
|
@@ -60,18 +75,28 @@ class BulkConvert
|
|
60 |
];
|
61 |
}
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
if (Paths::isPluginDirMovedOutOfWpContent()) {
|
64 |
$groups[] = [
|
65 |
'groupName' => 'plugins',
|
66 |
'root' => Paths::getPluginDirAbs(),
|
67 |
];
|
68 |
-
}
|
69 |
|
70 |
foreach ($groups as $i => &$group) {
|
71 |
$listOptions['root'] = $group['root'];
|
72 |
/*
|
73 |
No use, because if uploads is in wp-content, the cache root will be different for the files in uploads (if mingled)
|
74 |
-
$group['
|
75 |
$group['root'],
|
76 |
$listOptions['destination-folder'],
|
77 |
$listOptions['ext'],
|
@@ -79,11 +104,9 @@ class BulkConvert
|
|
79 |
$listOptions['uploadDirAbs']
|
80 |
);*/
|
81 |
$group['files'] = self::getListRecursively('.', $listOptions);
|
82 |
-
//'
|
83 |
}
|
84 |
|
85 |
-
|
86 |
-
|
87 |
return $groups;
|
88 |
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
|
89 |
}
|
@@ -133,13 +156,15 @@ class BulkConvert
|
|
133 |
$addThis = true;
|
134 |
|
135 |
if (($filter['only-converted']) || ($filter['only-unconverted'])) {
|
136 |
-
//$cacheDir = $listOptions['
|
137 |
$destination = ConvertHelperIndependent::getDestination(
|
138 |
$dir . "/" . $filename,
|
139 |
$listOptions['destination-folder'],
|
140 |
$listOptions['ext'],
|
141 |
$listOptions['webExpressContentDirAbs'],
|
142 |
-
$listOptions['uploadDirAbs']
|
|
|
|
|
143 |
);
|
144 |
$webpExists = @file_exists($destination);
|
145 |
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
|
|
|
|
|
|
|
|
5 |
class BulkConvert
|
6 |
{
|
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
public static function getList($config)
|
9 |
{
|
|
|
10 |
|
11 |
|
12 |
/*
|
19 |
|
20 |
$listOptions = [
|
21 |
//'root' => Paths::getUploadDirAbs(),
|
|
|
22 |
'ext' => $config['destination-extension'],
|
23 |
'destination-folder' => $config['destination-folder'], /* hm, "destination-folder" is a bad name... */
|
24 |
'webExpressContentDirAbs' => Paths::getWebPExpressContentDirAbs(),
|
25 |
'uploadDirAbs' => Paths::getUploadDirAbs(),
|
26 |
+
'useDocRootForStructuringCacheDir' => (($config['destination-structure'] == 'doc-root') && (Paths::canUseDocRootForStructuringCacheDir())),
|
27 |
+
'imageRoots' => new ImageRoots(Paths::getImageRootsDefForSelectedIds($config['scope'])), // (Paths::getImageRootsDef()
|
28 |
'filter' => [
|
29 |
'only-converted' => false,
|
30 |
'only-unconverted' => true,
|
32 |
]
|
33 |
];
|
34 |
|
35 |
+
//$dirs = $config['scope'];
|
36 |
+
|
37 |
+
$rootIds = Paths::filterOutSubRoots($config['scope']);
|
38 |
+
|
39 |
$groups = [];
|
40 |
+
foreach ($rootIds as $rootId) {
|
41 |
+
$groups[] = [
|
42 |
+
'groupName' => $rootId,
|
43 |
+
'root' => Paths::getAbsDirById($rootId)
|
44 |
+
];
|
45 |
+
}
|
46 |
|
47 |
+
/*
|
48 |
+
if (in_array('index', $config['scope'])) {
|
49 |
+
if (Paths::isWPContentDirMovedOutOfAbsPath()) {
|
50 |
+
|
51 |
+
}
|
52 |
+
} elseif (in_array('wp-content', $config['scope'])) {
|
53 |
+
$dirs[] = 'wp-content';
|
54 |
+
if (in_array('uploads', $config['scope']) && Paths::isUploadDirMovedOutOfWPContentDir()) {
|
55 |
+
$dirs[] = 'uploads';
|
56 |
+
}
|
57 |
+
if (in_array('plugins', $config['scope']) && Paths::isPluginDirMovedOutOfWpContent()) {
|
58 |
+
$dirs[] = 'plugins';
|
59 |
+
}
|
60 |
+
// ps: themes is always below wp-content
|
61 |
+
} else {
|
62 |
+
if (in_array('uploads', $config['scope'])) {
|
63 |
+
$dirs[] = 'uploads';
|
64 |
+
}
|
65 |
+
if (in_array('plugins', $config['scope'])) {
|
66 |
+
$dirs[] = 'plugins';
|
67 |
+
}
|
68 |
+
}
|
69 |
|
70 |
+
/*
|
71 |
if (Paths::isUploadDirMovedOutOfWPContentDir()) {
|
72 |
$groups[] = [
|
73 |
'groupName' => 'uploads',
|
75 |
];
|
76 |
}
|
77 |
|
78 |
+
$groups[] = [
|
79 |
+
'groupName' => 'themes',
|
80 |
+
'root' => Paths::getThemesDirAbs(),
|
81 |
+
];
|
82 |
+
|
83 |
+
$groups[] = [
|
84 |
+
'groupName' => 'wp-content',
|
85 |
+
'root' => Paths::getContentDirAbs(),
|
86 |
+
];
|
87 |
+
|
88 |
if (Paths::isPluginDirMovedOutOfWpContent()) {
|
89 |
$groups[] = [
|
90 |
'groupName' => 'plugins',
|
91 |
'root' => Paths::getPluginDirAbs(),
|
92 |
];
|
93 |
+
}*/
|
94 |
|
95 |
foreach ($groups as $i => &$group) {
|
96 |
$listOptions['root'] = $group['root'];
|
97 |
/*
|
98 |
No use, because if uploads is in wp-content, the cache root will be different for the files in uploads (if mingled)
|
99 |
+
$group['image-root'] = ConvertHelperIndependent::getDestinationFolder(
|
100 |
$group['root'],
|
101 |
$listOptions['destination-folder'],
|
102 |
$listOptions['ext'],
|
104 |
$listOptions['uploadDirAbs']
|
105 |
);*/
|
106 |
$group['files'] = self::getListRecursively('.', $listOptions);
|
107 |
+
//'image-root' => ConvertHelperIndependent::getDestinationFolder()
|
108 |
}
|
109 |
|
|
|
|
|
110 |
return $groups;
|
111 |
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
|
112 |
}
|
156 |
$addThis = true;
|
157 |
|
158 |
if (($filter['only-converted']) || ($filter['only-unconverted'])) {
|
159 |
+
//$cacheDir = $listOptions['image-root'] . '/' . $relDir;
|
160 |
$destination = ConvertHelperIndependent::getDestination(
|
161 |
$dir . "/" . $filename,
|
162 |
$listOptions['destination-folder'],
|
163 |
$listOptions['ext'],
|
164 |
$listOptions['webExpressContentDirAbs'],
|
165 |
+
$listOptions['uploadDirAbs'],
|
166 |
+
$listOptions['useDocRootForStructuringCacheDir'],
|
167 |
+
$listOptions['imageRoots']
|
168 |
);
|
169 |
$webpExists = @file_exists($destination);
|
170 |
|
lib/classes/CacheMover.php
CHANGED
@@ -3,6 +3,7 @@
|
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
use \WebPExpress\FileHelper;
|
|
|
6 |
use \WebPExpress\Paths;
|
7 |
|
8 |
class CacheMover
|
@@ -45,15 +46,64 @@ class CacheMover
|
|
45 |
FileHelper::chmod_r($dir, $dirPerm, $filePerm, $uid, $gid, '#\.webp$#', ($alsoSetOnDirs ? null : '#^$#'));
|
46 |
}
|
47 |
|
|
|
|
|
|
|
|
|
|
|
48 |
/**
|
49 |
* Move cache because of change in options.
|
50 |
-
*
|
51 |
* Only move those that has an original
|
52 |
* Only move those that can be moved.
|
53 |
* @return [$numFilesMoved, $numFilesFailedMoving]
|
54 |
*/
|
55 |
public static function move($newConfig, $oldConfig)
|
56 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
$fromDir = self::getUploadFolder($oldConfig['destination-folder']);
|
58 |
$fromExt = $oldConfig['destination-extension'];
|
59 |
|
@@ -62,6 +112,12 @@ class CacheMover
|
|
62 |
|
63 |
$srcDir = self::getUploadFolder('mingled');
|
64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
// for testing!
|
66 |
/*
|
67 |
$fromDir = self::getUploadFolder('mingled'); // separate | mingled
|
@@ -77,10 +133,6 @@ class CacheMover
|
|
77 |
|
78 |
//error_log('move to:' . $toDir . ' ( ' . (file_exists($toDir) ? 'exists' : 'does not exist ') . ')');
|
79 |
|
80 |
-
$result = self::moveRecursively($fromDir, $toDir, $srcDir, $fromExt, $toExt);
|
81 |
-
self::chmodFixSubDirs($toDir, ($newConfig['destination-folder'] == 'separate'));
|
82 |
-
|
83 |
-
return $result;
|
84 |
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
|
85 |
}
|
86 |
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
use \WebPExpress\FileHelper;
|
6 |
+
use \WebPExpress\PathHelper;
|
7 |
use \WebPExpress\Paths;
|
8 |
|
9 |
class CacheMover
|
46 |
FileHelper::chmod_r($dir, $dirPerm, $filePerm, $uid, $gid, '#\.webp$#', ($alsoSetOnDirs ? null : '#^$#'));
|
47 |
}
|
48 |
|
49 |
+
public static function getDestinationFolderForImageRoot($config, $imageRootId)
|
50 |
+
{
|
51 |
+
return Paths::getCacheDirForImageRoot($config['destination-folder'], $config['destination-structure'], $imageRootId);
|
52 |
+
}
|
53 |
+
|
54 |
/**
|
55 |
* Move cache because of change in options.
|
56 |
+
* If structure is unchanged, only move the upload folder
|
57 |
* Only move those that has an original
|
58 |
* Only move those that can be moved.
|
59 |
* @return [$numFilesMoved, $numFilesFailedMoving]
|
60 |
*/
|
61 |
public static function move($newConfig, $oldConfig)
|
62 |
{
|
63 |
+
if (!Paths::canUseDocRootForStructuringCacheDir()) {
|
64 |
+
if (($oldConfig['destination-structure'] == 'doc-root') || ($newConfig['destination-structure'] == 'doc-root')) {
|
65 |
+
// oh, well. Seems document root is not available.
|
66 |
+
// so we cannot move from or to that kind of structure
|
67 |
+
// This could happen if document root once was available but now is unavailable
|
68 |
+
return [0, 0];
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
$changeStructure = ($newConfig['destination-structure'] != $oldConfig['destination-structure']);
|
73 |
+
|
74 |
+
if ($changeStructure) {
|
75 |
+
$rootIds = Paths::getImageRootIds();
|
76 |
+
} else {
|
77 |
+
$rootIds = ['uploads'];
|
78 |
+
}
|
79 |
+
|
80 |
+
$numFilesMovedTotal = 0;
|
81 |
+
$numFilesFailedMovingTotal = 0;
|
82 |
+
foreach ($rootIds as $rootId) {
|
83 |
+
|
84 |
+
$isUploadsMingled = (($newConfig['destination-folder'] == 'mingled') && ($rootId == 'uploads'));
|
85 |
+
|
86 |
+
$fromDir = self::getDestinationFolderForImageRoot($oldConfig, $rootId);
|
87 |
+
$fromExt = $oldConfig['destination-extension'];
|
88 |
+
|
89 |
+
$toDir = self::getDestinationFolderForImageRoot($newConfig, $rootId);
|
90 |
+
$toExt = $newConfig['destination-extension'];
|
91 |
+
|
92 |
+
$srcDir = Paths::getAbsDirById($rootId);
|
93 |
+
|
94 |
+
list($numFilesMoved, $numFilesFailedMoving) = self::moveRecursively($fromDir, $toDir, $srcDir, $fromExt, $toExt);
|
95 |
+
if (!$isUploadsMingled) {
|
96 |
+
FileHelper::removeEmptySubFolders($fromDir);
|
97 |
+
}
|
98 |
+
|
99 |
+
$numFilesMovedTotal += $numFilesMoved;
|
100 |
+
$numFilesFailedMovingTotal += $numFilesFailedMoving;
|
101 |
+
|
102 |
+
$chmodFixFoldersToo = !$isUploadsMingled;
|
103 |
+
self::chmodFixSubDirs($toDir, $chmodFixFoldersToo);
|
104 |
+
}
|
105 |
+
return [$numFilesMovedTotal, $numFilesFailedMovingTotal];
|
106 |
+
/*
|
107 |
$fromDir = self::getUploadFolder($oldConfig['destination-folder']);
|
108 |
$fromExt = $oldConfig['destination-extension'];
|
109 |
|
112 |
|
113 |
$srcDir = self::getUploadFolder('mingled');
|
114 |
|
115 |
+
$result = self::moveRecursively($fromDir, $toDir, $srcDir, $fromExt, $toExt);
|
116 |
+
self::chmodFixSubDirs($toDir, ($newConfig['destination-folder'] == 'separate'));
|
117 |
+
*/
|
118 |
+
|
119 |
+
//return $result;
|
120 |
+
|
121 |
// for testing!
|
122 |
/*
|
123 |
$fromDir = self::getUploadFolder('mingled'); // separate | mingled
|
133 |
|
134 |
//error_log('move to:' . $toDir . ' ( ' . (file_exists($toDir) ? 'exists' : 'does not exist ') . ')');
|
135 |
|
|
|
|
|
|
|
|
|
136 |
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
|
137 |
}
|
138 |
|
lib/classes/CachePurge.php
CHANGED
@@ -7,6 +7,8 @@ use \WebPExpress\FileHelper;
|
|
7 |
use \WebPExpress\DismissableMessages;
|
8 |
use \WebPExpress\Paths;
|
9 |
|
|
|
|
|
10 |
class CachePurge
|
11 |
{
|
12 |
|
@@ -27,6 +29,7 @@ class CachePurge
|
|
27 |
$numFailed = 0;
|
28 |
|
29 |
list($numDeleted, $numFailed) = self::purgeWebPFilesInDir(Paths::getCacheDirAbs(), $filter, $config);
|
|
|
30 |
|
31 |
if ($config['destination-folder'] == 'mingled') {
|
32 |
list($d, $f) = self::purgeWebPFilesInDir(Paths::getUploadDirAbs(), $filter, $config);
|
@@ -35,6 +38,7 @@ class CachePurge
|
|
35 |
$numFailed += $f;
|
36 |
}
|
37 |
|
|
|
38 |
return [
|
39 |
'delete-count' => $numDeleted,
|
40 |
'fail-count' => $numFailed
|
7 |
use \WebPExpress\DismissableMessages;
|
8 |
use \WebPExpress\Paths;
|
9 |
|
10 |
+
// TODO! Needs to be updated to work with the new "destination-structure" setting
|
11 |
+
|
12 |
class CachePurge
|
13 |
{
|
14 |
|
29 |
$numFailed = 0;
|
30 |
|
31 |
list($numDeleted, $numFailed) = self::purgeWebPFilesInDir(Paths::getCacheDirAbs(), $filter, $config);
|
32 |
+
FileHelper::removeEmptySubFolders(Paths::getCacheDirAbs());
|
33 |
|
34 |
if ($config['destination-folder'] == 'mingled') {
|
35 |
list($d, $f) = self::purgeWebPFilesInDir(Paths::getUploadDirAbs(), $filter, $config);
|
38 |
$numFailed += $f;
|
39 |
}
|
40 |
|
41 |
+
|
42 |
return [
|
43 |
'delete-count' => $numDeleted,
|
44 |
'fail-count' => $numFailed
|
lib/classes/CapabilityTest.php
CHANGED
@@ -1,5 +1,11 @@
|
|
1 |
<?php
|
|
|
|
|
2 |
|
|
|
|
|
|
|
|
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
use \WebPExpress\FileHelper;
|
1 |
<?php
|
2 |
+
/*
|
3 |
+
This functionality will be moved to a separate project.
|
4 |
|
5 |
+
Btw:
|
6 |
+
Seems someone else got similar idea:
|
7 |
+
http://christian.roy.name/blog/detecting-modrewrite-using-php
|
8 |
+
*/
|
9 |
namespace WebPExpress;
|
10 |
|
11 |
use \WebPExpress\FileHelper;
|
lib/classes/Config.php
CHANGED
@@ -15,7 +15,7 @@ class Config
|
|
15 |
{
|
16 |
|
17 |
/**
|
18 |
-
*
|
19 |
*/
|
20 |
public static function loadJSONOptions($filename)
|
21 |
{
|
@@ -33,7 +33,10 @@ class Config
|
|
33 |
|
34 |
public static function saveJSONOptions($filename, $obj)
|
35 |
{
|
36 |
-
$result = @file_put_contents(
|
|
|
|
|
|
|
37 |
/*if ($result === false) {
|
38 |
echo 'COULD NOT' . $filename;
|
39 |
}*/
|
@@ -41,6 +44,9 @@ class Config
|
|
41 |
}
|
42 |
|
43 |
|
|
|
|
|
|
|
44 |
public static function loadConfig()
|
45 |
{
|
46 |
return self::loadJSONOptions(Paths::getConfigFileName());
|
@@ -61,10 +67,12 @@ class Config
|
|
61 |
'image-types' => 3,
|
62 |
'destination-folder' => 'separate',
|
63 |
'destination-extension' => 'append',
|
|
|
64 |
'cache-control' => 'no-header', /* can be "no-header", "set" or "custom" */
|
65 |
'cache-control-custom' => 'public, max-age=31536000, stale-while-revalidate=604800, stale-if-error=604800',
|
66 |
'cache-control-max-age' => 'one-week',
|
67 |
'cache-control-public' => false,
|
|
|
68 |
|
69 |
// redirection rules
|
70 |
'enable-redirection-to-converter' => true,
|
@@ -121,7 +129,13 @@ class Config
|
|
121 |
]
|
122 |
*/
|
123 |
]
|
|
|
124 |
|
|
|
|
|
|
|
|
|
|
|
125 |
]
|
126 |
];
|
127 |
}
|
@@ -170,6 +184,7 @@ class Config
|
|
170 |
]);
|
171 |
$config['alter-html']['only-for-webps-that-exists'] = true;
|
172 |
$config['web-service']['enabled'] = false;
|
|
|
173 |
|
174 |
}
|
175 |
|
@@ -192,15 +207,41 @@ class Config
|
|
192 |
$defaultConfig = self::getDefaultConfig(true);
|
193 |
$config = array_merge($defaultConfig, $config);
|
194 |
|
|
|
|
|
195 |
$config['alter-html'] = array_replace_recursive($defaultConfig['alter-html'], $config['alter-html']);
|
|
|
|
|
|
|
196 |
}
|
197 |
|
198 |
if (!isset($config['base-htaccess-on-these-capability-tests'])) {
|
199 |
self::runAndStoreCapabilityTests($config);
|
200 |
}
|
201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
$config = self::applyOperationMode($config);
|
203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
if (!isset($config['web-service'])) {
|
205 |
$config['web-service'] = [
|
206 |
'enabled' => false
|
@@ -425,16 +466,44 @@ class Config
|
|
425 |
unset($obj['enabled']);
|
426 |
$obj['destination-folder'] = $config['destination-folder'];
|
427 |
$obj['destination-extension'] = $config['destination-extension'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
428 |
$obj['bases'] = [
|
429 |
'uploads' => [
|
430 |
Paths::getUploadDirAbs(),
|
431 |
Paths::getUploadUrl()
|
432 |
],
|
433 |
-
|
|
|
|
|
|
|
434 |
Paths::getContentDirAbs(),
|
435 |
Paths::getContentUrl()
|
436 |
-
]
|
437 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
438 |
$obj['image-types'] = $config['image-types']; // 0=none,1=jpg, 2=png, 3=both
|
439 |
|
440 |
Option::updateOption(
|
@@ -447,9 +516,7 @@ class Config
|
|
447 |
public static function saveConfigurationFile($config)
|
448 |
{
|
449 |
$config['paths-used-in-htaccess'] = [
|
450 |
-
'existing' => Paths::getPathToExisting(),
|
451 |
'wod-url-path' => Paths::getWodUrlPath(),
|
452 |
-
'config-dir-rel' => Paths::getConfigDirRel()
|
453 |
];
|
454 |
|
455 |
if (Paths::createConfigDirIfMissing()) {
|
@@ -609,9 +676,7 @@ class Config
|
|
609 |
'destination-folder' => $config['destination-folder'],
|
610 |
'forward-query-string' => $config['forward-query-string'],
|
611 |
//'method-for-passing-source' => $config['method-for-passing-source'],
|
612 |
-
'
|
613 |
-
'uploadDirRel' => Paths::getUploadDirRel()
|
614 |
-
],
|
615 |
'success-response' => $config['success-response'],
|
616 |
];
|
617 |
|
15 |
{
|
16 |
|
17 |
/**
|
18 |
+
* @return object|false Returns parsed file the file exists and can be read. Otherwise it returns false
|
19 |
*/
|
20 |
public static function loadJSONOptions($filename)
|
21 |
{
|
33 |
|
34 |
public static function saveJSONOptions($filename, $obj)
|
35 |
{
|
36 |
+
$result = @file_put_contents(
|
37 |
+
$filename,
|
38 |
+
json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT)
|
39 |
+
);
|
40 |
/*if ($result === false) {
|
41 |
echo 'COULD NOT' . $filename;
|
42 |
}*/
|
44 |
}
|
45 |
|
46 |
|
47 |
+
/**
|
48 |
+
* @return object|false Returns config object if config file exists and can be read. Otherwise it returns false
|
49 |
+
*/
|
50 |
public static function loadConfig()
|
51 |
{
|
52 |
return self::loadJSONOptions(Paths::getConfigFileName());
|
67 |
'image-types' => 3,
|
68 |
'destination-folder' => 'separate',
|
69 |
'destination-extension' => 'append',
|
70 |
+
'destination-structure' => 'doc-root', /* can be "doc-root" or "image-roots" */
|
71 |
'cache-control' => 'no-header', /* can be "no-header", "set" or "custom" */
|
72 |
'cache-control-custom' => 'public, max-age=31536000, stale-while-revalidate=604800, stale-if-error=604800',
|
73 |
'cache-control-max-age' => 'one-week',
|
74 |
'cache-control-public' => false,
|
75 |
+
'scope' => ['themes', 'uploads'],
|
76 |
|
77 |
// redirection rules
|
78 |
'enable-redirection-to-converter' => true,
|
129 |
]
|
130 |
*/
|
131 |
]
|
132 |
+
],
|
133 |
|
134 |
+
'environment-when-config-was-saved' => [
|
135 |
+
'doc-root-available' => null, // null means unavailable
|
136 |
+
'doc-root-resolvable' => null,
|
137 |
+
'doc-root-usable-for-structuring' => null,
|
138 |
+
'image-roots' => null,
|
139 |
]
|
140 |
];
|
141 |
}
|
184 |
]);
|
185 |
$config['alter-html']['only-for-webps-that-exists'] = true;
|
186 |
$config['web-service']['enabled'] = false;
|
187 |
+
$config['scope'] = ['uploads'];
|
188 |
|
189 |
}
|
190 |
|
207 |
$defaultConfig = self::getDefaultConfig(true);
|
208 |
$config = array_merge($defaultConfig, $config);
|
209 |
|
210 |
+
// Make sure new defaults below "alter-html" are added into the existing array
|
211 |
+
// (note that this will not remove old unused properties, if some key should become obsolete)
|
212 |
$config['alter-html'] = array_replace_recursive($defaultConfig['alter-html'], $config['alter-html']);
|
213 |
+
|
214 |
+
// Make sure new defaults below "environment-when-config-was-saved" are added into the existing array
|
215 |
+
$config['environment-when-config-was-saved'] = array_replace_recursive($defaultConfig['environment-when-config-was-saved'], $config['environment-when-config-was-saved']);
|
216 |
}
|
217 |
|
218 |
if (!isset($config['base-htaccess-on-these-capability-tests'])) {
|
219 |
self::runAndStoreCapabilityTests($config);
|
220 |
}
|
221 |
|
222 |
+
// Apparently, migrate7 did not fix old "operation-mode" values for all.
|
223 |
+
// So fix here
|
224 |
+
if ($config['operation-mode'] == 'just-redirect') {
|
225 |
+
$config['operation-mode'] = 'no-conversion';
|
226 |
+
}
|
227 |
+
if ($config['operation-mode'] == 'no-varied-responses') {
|
228 |
+
$config['operation-mode'] = 'cdn-friendly';
|
229 |
+
}
|
230 |
+
if ($config['operation-mode'] == 'varied-responses') {
|
231 |
+
$config['operation-mode'] = 'varied-image-responses';
|
232 |
+
}
|
233 |
+
|
234 |
$config = self::applyOperationMode($config);
|
235 |
|
236 |
+
// Fix scope: Remove invalid and put in correct order
|
237 |
+
$fixedScope = [];
|
238 |
+
foreach (Paths::getImageRootIds() as $rootId) {
|
239 |
+
if (in_array($rootId, $config['scope'])) {
|
240 |
+
$fixedScope[] = $rootId;
|
241 |
+
}
|
242 |
+
}
|
243 |
+
$config['scope'] = $fixedScope;
|
244 |
+
|
245 |
if (!isset($config['web-service'])) {
|
246 |
$config['web-service'] = [
|
247 |
'enabled' => false
|
466 |
unset($obj['enabled']);
|
467 |
$obj['destination-folder'] = $config['destination-folder'];
|
468 |
$obj['destination-extension'] = $config['destination-extension'];
|
469 |
+
$obj['destination-structure'] = $config['destination-structure'];
|
470 |
+
|
471 |
+
|
472 |
+
$obj['bases'] = [];
|
473 |
+
foreach ($config['scope'] as $rootId) {
|
474 |
+
$obj['bases'][$rootId] = [
|
475 |
+
Paths::getAbsDirById($rootId),
|
476 |
+
Paths::getUrlById($rootId)
|
477 |
+
];
|
478 |
+
}
|
479 |
+
|
480 |
+
/*
|
481 |
+
// TODO!
|
482 |
+
// Instead of "bases", use image root ids.
|
483 |
+
// Its a numeric array and there is no id called "content"
|
484 |
+
|
485 |
$obj['bases'] = [
|
486 |
'uploads' => [
|
487 |
Paths::getUploadDirAbs(),
|
488 |
Paths::getUploadUrl()
|
489 |
],
|
490 |
+
];
|
491 |
+
|
492 |
+
if ($obj['destination-structure'] == 'doc-root') {
|
493 |
+
$obj['bases']['content'] = [
|
494 |
Paths::getContentDirAbs(),
|
495 |
Paths::getContentUrl()
|
496 |
+
];
|
497 |
+
} else {
|
498 |
+
foreach (Paths::getImageRootIds() as $rootId) {
|
499 |
+
$obj['bases'][$rootId] = [
|
500 |
+
Paths::getAbsDirById($rootId),
|
501 |
+
Paths::getUrlById($rootId)
|
502 |
+
];
|
503 |
+
}
|
504 |
+
}*/
|
505 |
+
|
506 |
+
|
507 |
$obj['image-types'] = $config['image-types']; // 0=none,1=jpg, 2=png, 3=both
|
508 |
|
509 |
Option::updateOption(
|
516 |
public static function saveConfigurationFile($config)
|
517 |
{
|
518 |
$config['paths-used-in-htaccess'] = [
|
|
|
519 |
'wod-url-path' => Paths::getWodUrlPath(),
|
|
|
520 |
];
|
521 |
|
522 |
if (Paths::createConfigDirIfMissing()) {
|
676 |
'destination-folder' => $config['destination-folder'],
|
677 |
'forward-query-string' => $config['forward-query-string'],
|
678 |
//'method-for-passing-source' => $config['method-for-passing-source'],
|
679 |
+
'image-roots' => Paths::getImageRootsDef(),
|
|
|
|
|
680 |
'success-response' => $config['success-response'],
|
681 |
];
|
682 |
|
lib/classes/Convert.php
CHANGED
@@ -5,6 +5,7 @@ namespace WebPExpress;
|
|
5 |
use \WebPExpress\ConvertHelperIndependent;
|
6 |
use \WebPExpress\Config;
|
7 |
use \WebPExpress\ConvertersHelper;
|
|
|
8 |
use \WebPExpress\SanityCheck;
|
9 |
use \WebPExpress\SanityException;
|
10 |
use \WebPExpress\Validate;
|
@@ -23,7 +24,9 @@ class Convert
|
|
23 |
$config['destination-folder'],
|
24 |
$config['destination-extension'],
|
25 |
Paths::getWebPExpressContentDirAbs(),
|
26 |
-
Paths::getUploadDirAbs()
|
|
|
|
|
27 |
);
|
28 |
}
|
29 |
|
@@ -32,11 +35,17 @@ class Convert
|
|
32 |
try {
|
33 |
// Check source
|
34 |
// ---------------
|
35 |
-
$checking = '
|
36 |
-
$
|
37 |
//$filename = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
38 |
// PS: No need to check mime type as the WebPConvert library does that (it only accepts image/jpeg and image/png)
|
39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
// Check config
|
42 |
// --------------
|
@@ -76,22 +85,38 @@ class Convert
|
|
76 |
$logDir = SanityCheck::absPathIsInDocRoot(Paths::getWebPExpressContentDirAbs() . '/log');
|
77 |
|
78 |
|
79 |
-
} catch (
|
80 |
return [
|
81 |
'success' => false,
|
82 |
-
'msg' => '
|
83 |
'log' => '',
|
84 |
];
|
85 |
}
|
86 |
|
87 |
// Done with sanitizing, lets get to work!
|
88 |
// ---------------------------------------
|
89 |
-
|
90 |
$result = ConvertHelperIndependent::convert($source, $destination, $convertOptions, $logDir, $converter);
|
91 |
|
92 |
if ($result['success'] === true) {
|
93 |
$result['filesize-original'] = @filesize($source);
|
94 |
$result['filesize-webp'] = @filesize($destination);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
}
|
96 |
return $result;
|
97 |
}
|
@@ -127,7 +152,9 @@ class Convert
|
|
127 |
$destination,
|
128 |
$config['destination-folder'],
|
129 |
$config['destination-extension'],
|
130 |
-
|
|
|
|
|
131 |
);
|
132 |
}
|
133 |
|
@@ -145,7 +172,12 @@ class Convert
|
|
145 |
// Check "filename"
|
146 |
$checking = '"filename" argument';
|
147 |
Validate::postHasKey('filename');
|
148 |
-
|
|
|
|
|
|
|
|
|
|
|
149 |
//$filename = SanityCheck::absPathExistsAndIsFileInDocRoot($filename);
|
150 |
// PS: No need to check mime version as webp-convert does that.
|
151 |
|
5 |
use \WebPExpress\ConvertHelperIndependent;
|
6 |
use \WebPExpress\Config;
|
7 |
use \WebPExpress\ConvertersHelper;
|
8 |
+
use \WebPExpress\ImageRoots;
|
9 |
use \WebPExpress\SanityCheck;
|
10 |
use \WebPExpress\SanityException;
|
11 |
use \WebPExpress\Validate;
|
24 |
$config['destination-folder'],
|
25 |
$config['destination-extension'],
|
26 |
Paths::getWebPExpressContentDirAbs(),
|
27 |
+
Paths::getUploadDirAbs(),
|
28 |
+
(($config['destination-structure'] == 'doc-root') && (Paths::canUseDocRootForStructuringCacheDir())),
|
29 |
+
new ImageRoots(Paths::getImageRootsDef())
|
30 |
);
|
31 |
}
|
32 |
|
35 |
try {
|
36 |
// Check source
|
37 |
// ---------------
|
38 |
+
$checking = 'source path';
|
39 |
+
$source = SanityCheck::absPathExistsAndIsFile($source);
|
40 |
//$filename = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
41 |
// PS: No need to check mime type as the WebPConvert library does that (it only accepts image/jpeg and image/png)
|
42 |
|
43 |
+
// Check that source is within a valid image root
|
44 |
+
$activeRootIds = Paths::getImageRootIds(); // Currently, root ids cannot be selected, so all root ids are active.
|
45 |
+
$rootId = Paths::findImageRootOfPath($source, $activeRootIds);
|
46 |
+
if ($rootId === false) {
|
47 |
+
throw new Exception('Path of source is not within a valid image root');
|
48 |
+
}
|
49 |
|
50 |
// Check config
|
51 |
// --------------
|
85 |
$logDir = SanityCheck::absPathIsInDocRoot(Paths::getWebPExpressContentDirAbs() . '/log');
|
86 |
|
87 |
|
88 |
+
} catch (\Exception $e) {
|
89 |
return [
|
90 |
'success' => false,
|
91 |
+
'msg' => 'Check failed for ' . $checking . ': '. $e->getMessage(),
|
92 |
'log' => '',
|
93 |
];
|
94 |
}
|
95 |
|
96 |
// Done with sanitizing, lets get to work!
|
97 |
// ---------------------------------------
|
98 |
+
//return false;
|
99 |
$result = ConvertHelperIndependent::convert($source, $destination, $convertOptions, $logDir, $converter);
|
100 |
|
101 |
if ($result['success'] === true) {
|
102 |
$result['filesize-original'] = @filesize($source);
|
103 |
$result['filesize-webp'] = @filesize($destination);
|
104 |
+
$result['destination-path'] = $destination;
|
105 |
+
|
106 |
+
$rootOfDestination = Paths::destinationRoot($rootId, $config['destination-folder'], $config['destination-structure']);
|
107 |
+
|
108 |
+
$relPathFromImageRootToSource = PathHelper::getRelDir(
|
109 |
+
realpath(Paths::getAbsDirById($rootId)),
|
110 |
+
realpath($source)
|
111 |
+
);
|
112 |
+
$relPathFromImageRootToDest = ConvertHelperIndependent::appendOrSetExtension(
|
113 |
+
$relPathFromImageRootToSource,
|
114 |
+
$config['destination-folder'],
|
115 |
+
$config['destination-extension'],
|
116 |
+
($rootId == 'uploads')
|
117 |
+
);
|
118 |
+
|
119 |
+
$result['destination-url'] = $rootOfDestination['url'] . '/' . $relPathFromImageRootToDest;
|
120 |
}
|
121 |
return $result;
|
122 |
}
|
152 |
$destination,
|
153 |
$config['destination-folder'],
|
154 |
$config['destination-extension'],
|
155 |
+
$config['destination-structure'],
|
156 |
+
Paths::getWebPExpressContentDirAbs(),
|
157 |
+
new ImageRoots(Paths::getImageRootsDef())
|
158 |
);
|
159 |
}
|
160 |
|
172 |
// Check "filename"
|
173 |
$checking = '"filename" argument';
|
174 |
Validate::postHasKey('filename');
|
175 |
+
|
176 |
+
$filename = sanitize_text_field(stripslashes($_POST['filename']));
|
177 |
+
|
178 |
+
// holy moly! Wordpress automatically adds slashes to the global POST vars - https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
|
179 |
+
$filename = wp_unslash($_POST['filename']);
|
180 |
+
|
181 |
//$filename = SanityCheck::absPathExistsAndIsFileInDocRoot($filename);
|
182 |
// PS: No need to check mime version as webp-convert does that.
|
183 |
|
lib/classes/ConvertHelperIndependent.php
CHANGED
@@ -47,6 +47,31 @@ class ConvertHelperIndependent
|
|
47 |
return strpos($normalizedSource, $normalizedDocRoot) === 0;
|
48 |
}
|
49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
/**
|
52 |
* Get destination path corresponding to the source path given (and some configurations)
|
@@ -54,15 +79,24 @@ class ConvertHelperIndependent
|
|
54 |
* If for example Operation mode is set to "mingled" and extension is set to "Append .webp",
|
55 |
* the result of finding the destination path that corresponds to "/path/to/logo.jpg" will be "/path/to/logo.jpg.webp".
|
56 |
*
|
57 |
-
* @param string
|
58 |
-
* @param string
|
59 |
-
* @param string
|
60 |
-
* @param string
|
61 |
-
* @param string
|
|
|
|
|
62 |
*
|
63 |
* @return string|false Returns path to destination corresponding to source, or false on failure
|
64 |
*/
|
65 |
-
public static function getDestination(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
{
|
67 |
// At this point, everything has already been checked for sanity. But for good meassure, lets
|
68 |
// check the most important parts again. This is after all a public method.
|
@@ -77,25 +111,113 @@ class ConvertHelperIndependent
|
|
77 |
// Calculate destination and check that the result is sane
|
78 |
// -------------------------------------------------------
|
79 |
if (self::storeMingledOrNot($source, $destinationFolder, $uploadDirAbs)) {
|
80 |
-
|
81 |
-
// TODO: make this check work with symlinks
|
82 |
-
//$destination = SanityCheck::absPathIsInDocRoot($source . '.webp');
|
83 |
-
$destination = $source . '.webp';
|
84 |
-
} else {
|
85 |
-
$destination = preg_replace('/\\.(jpe?g|png)$/', '', $source) . '.webp';
|
86 |
-
}
|
87 |
} else {
|
88 |
-
$docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
|
89 |
-
$imageRoot = $webExpressContentDirAbs . '/webp-images';
|
90 |
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
|
94 |
-
$sourceRel = substr($source, strlen($docRoot) + 1);
|
95 |
-
$destination = $imageRoot . '/doc-root/' . $sourceRel . '.webp';
|
96 |
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
}
|
100 |
|
101 |
} catch (SanityException $e) {
|
@@ -113,47 +235,140 @@ class ConvertHelperIndependent
|
|
113 |
* Returns false if source file is not found or if a path is not sane. Otherwise returns path to source
|
114 |
* destination does not have to exist.
|
115 |
*
|
116 |
-
* @param string
|
117 |
-
* @param string
|
|
|
|
|
118 |
*
|
119 |
* @return string|false Returns path to source, if found. If not - or a path is not sane, false is returned
|
120 |
*/
|
121 |
-
private static function findSourceSeparate($destination, $webExpressContentDirAbs)
|
122 |
{
|
123 |
try {
|
124 |
|
125 |
-
|
126 |
-
// --------------------------
|
127 |
-
$destination = SanityCheck::absPathIsInDocRoot($destination);
|
128 |
|
|
|
|
|
|
|
129 |
|
130 |
-
// Check that calculated image root is sane and inside document root
|
131 |
-
// --------------------------
|
132 |
-
$imageRoot = SanityCheck::absPathIsInDocRoot($webExpressContentDirAbs . '/webp-images/doc-root');
|
133 |
|
|
|
|
|
|
|
134 |
|
135 |
-
// Calculate source and check that it is sane and exists
|
136 |
-
// -----------------------------------------------------
|
137 |
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
142 |
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
} else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
return false;
|
153 |
}
|
154 |
-
|
155 |
-
$source = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
156 |
-
|
157 |
} catch (SanityException $e) {
|
158 |
return false;
|
159 |
}
|
@@ -165,19 +380,24 @@ class ConvertHelperIndependent
|
|
165 |
* Find source corresponding to destination (mingled)
|
166 |
* Returns false if not found. Otherwise returns path to source
|
167 |
*
|
168 |
-
* @param string $destination
|
169 |
-
* @param string $destinationExt
|
|
|
170 |
*
|
171 |
* @return string|false Returns path to source, if found. If not - or a path is not sane, false is returned
|
172 |
*/
|
173 |
-
private static function findSourceMingled($destination, $destinationExt)
|
174 |
{
|
175 |
try {
|
176 |
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
|
|
|
|
|
|
|
|
181 |
|
182 |
// Calculate source and check that it is sane and exists
|
183 |
// -----------------------------------------------------
|
@@ -192,7 +412,11 @@ class ConvertHelperIndependent
|
|
192 |
$source = preg_replace('/\\.webp$/', '.png', $destination);
|
193 |
}
|
194 |
}
|
195 |
-
$
|
|
|
|
|
|
|
|
|
196 |
|
197 |
|
198 |
} catch (SanityException $e) {
|
@@ -206,33 +430,41 @@ class ConvertHelperIndependent
|
|
206 |
* Get source from destination (and some configurations)
|
207 |
* Returns false if not found. Otherwise returns path to source
|
208 |
*
|
209 |
-
* @param string $destination Path to destination file (does not have to exist)
|
210 |
* @param string $destinationFolder 'mingled' or 'separate'
|
211 |
* @param string $destinationExt Extension ('append' or 'set')
|
|
|
212 |
* @param string $webExpressContentDirAbs
|
|
|
213 |
*
|
214 |
* @return string|false Returns path to source, if found. If not - or a path is not sane, false is returned
|
215 |
*/
|
216 |
-
public static function findSource($destination, $destinationFolder, $destinationExt, $webExpressContentDirAbs)
|
217 |
{
|
|
|
218 |
try {
|
219 |
|
220 |
-
|
221 |
-
|
222 |
-
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
} catch (SanityException $e) {
|
225 |
return false;
|
226 |
}
|
227 |
|
228 |
if ($destinationFolder == 'mingled') {
|
229 |
-
$result = self::findSourceMingled($destination, $destinationExt);
|
230 |
if ($result === false) {
|
231 |
-
$result = self::findSourceSeparate($destination, $webExpressContentDirAbs);
|
232 |
}
|
233 |
return $result;
|
234 |
} else {
|
235 |
-
return self::findSourceSeparate($destination, $webExpressContentDirAbs);
|
236 |
}
|
237 |
}
|
238 |
|
@@ -326,7 +558,7 @@ APACHE
|
|
326 |
|
327 |
$text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
|
328 |
|
329 |
-
$text = 'WebP Express 0.
|
330 |
|
331 |
$logFile = self::getLogFilename($source, $logDir);
|
332 |
|
@@ -348,8 +580,8 @@ APACHE
|
|
348 |
*
|
349 |
* PS: To convert with a specific converter, set it in the $converter param.
|
350 |
*
|
351 |
-
* @param string $source
|
352 |
-
* @param string $destination
|
353 |
* @param array $convertOptions Conversion options.
|
354 |
* @param string $logDir The folder where log files are kept.
|
355 |
* @param string $converter (optional) Set it to convert with a specific converter.
|
@@ -436,20 +668,23 @@ APACHE
|
|
436 |
// ---------------------------------------------
|
437 |
try {
|
438 |
|
439 |
-
// Check that source path is sane, exists, is a file
|
440 |
// -------------------------------------------------------
|
441 |
-
|
|
|
442 |
|
443 |
|
444 |
-
// Check that destination path is sane
|
445 |
// -------------------------------------------------------
|
446 |
-
|
|
|
447 |
$destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Destination does not end with .webp');
|
448 |
|
449 |
|
450 |
-
// Check that log path is sane
|
451 |
// -------------------------------------------------------
|
452 |
-
|
|
|
453 |
|
454 |
|
455 |
// PS: No need to check $logMsgTop. Log files are markdown and stored as ".md". They can do no harm.
|
47 |
return strpos($normalizedSource, $normalizedDocRoot) === 0;
|
48 |
}
|
49 |
|
50 |
+
public static function getSource()
|
51 |
+
{
|
52 |
+
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Append ".webp" to path or replace extension with "webp", depending on what is appropriate.
|
57 |
+
*
|
58 |
+
* If destination-folder is set to mingled and destination-extension is set to "set" and
|
59 |
+
* the path is inside upload folder, the appropriate thing is to SET the extension.
|
60 |
+
* Otherwise, it is to APPEND.
|
61 |
+
*
|
62 |
+
* @param string $path
|
63 |
+
* @param string $destinationFolder
|
64 |
+
* @param string $destinationExt
|
65 |
+
* @param boolean $inUploadFolder
|
66 |
+
*/
|
67 |
+
public static function appendOrSetExtension($path, $destinationFolder, $destinationExt, $inUploadFolder)
|
68 |
+
{
|
69 |
+
if (($destinationFolder == 'mingled') && ($destinationExt == 'set') && $inUploadFolder) {
|
70 |
+
return preg_replace('/\\.(jpe?g|png)$/', '', $path) . '.webp';
|
71 |
+
} else {
|
72 |
+
return $path . '.webp';
|
73 |
+
}
|
74 |
+
}
|
75 |
|
76 |
/**
|
77 |
* Get destination path corresponding to the source path given (and some configurations)
|
79 |
* If for example Operation mode is set to "mingled" and extension is set to "Append .webp",
|
80 |
* the result of finding the destination path that corresponds to "/path/to/logo.jpg" will be "/path/to/logo.jpg.webp".
|
81 |
*
|
82 |
+
* @param string $source Path to source file
|
83 |
+
* @param string $destinationFolder 'mingled' or 'separate'
|
84 |
+
* @param string $destinationExt Extension ('append' or 'set')
|
85 |
+
* @param string $webExpressContentDirAbs
|
86 |
+
* @param string $uploadDirAbs
|
87 |
+
* @param boolean $useDocRootForStructuringCacheDir
|
88 |
+
* @param ImageRoots $imageRoots An image roots object
|
89 |
*
|
90 |
* @return string|false Returns path to destination corresponding to source, or false on failure
|
91 |
*/
|
92 |
+
public static function getDestination(
|
93 |
+
$source,
|
94 |
+
$destinationFolder,
|
95 |
+
$destinationExt,
|
96 |
+
$webExpressContentDirAbs,
|
97 |
+
$uploadDirAbs,
|
98 |
+
$useDocRootForStructuringCacheDir,
|
99 |
+
$imageRoots)
|
100 |
{
|
101 |
// At this point, everything has already been checked for sanity. But for good meassure, lets
|
102 |
// check the most important parts again. This is after all a public method.
|
111 |
// Calculate destination and check that the result is sane
|
112 |
// -------------------------------------------------------
|
113 |
if (self::storeMingledOrNot($source, $destinationFolder, $uploadDirAbs)) {
|
114 |
+
$destination = self::appendOrSetExtension($source, $destinationFolder, $destinationExt, true);
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
} else {
|
|
|
|
|
116 |
|
117 |
+
if ($useDocRootForStructuringCacheDir) {
|
118 |
+
// We must find the relative path from document root to source.
|
119 |
+
// However, we dont know if document root is resolved or not.
|
120 |
+
// We also do not know if source begins with a resolved or unresolved document root.
|
121 |
+
// And we cannot be sure that document root is resolvable.
|
122 |
+
|
123 |
+
// Lets say:
|
124 |
+
// 1. document root is unresolvable.
|
125 |
+
// 2. document root is configured to something unresolved ("/my-website")
|
126 |
+
// 3. source is resolved and within an image root ("/var/www/my-website/wp-content/uploads/test.jpg")
|
127 |
+
// 4. all image roots are resolvable.
|
128 |
+
// 5. Paths::canUseDocRootForRelPaths()) returned true
|
129 |
+
|
130 |
+
// Can the relative path then be found?
|
131 |
+
// Actually, yes.
|
132 |
+
// We can loop through the image roots.
|
133 |
+
// When we get to the "uploads" root, it must neccessarily contain the unresolved document root.
|
134 |
+
// It will in other words be: "my-website/wp-content/uploads"
|
135 |
+
// It can not be configured to the resolved path because canUseDocRootForRelPaths would have then returned false as
|
136 |
+
// It would not be possible to establish that "/var/www/my-website/wp-content/uploads/" is within document root, as
|
137 |
+
// document root is "/my-website" and unresolvable.
|
138 |
+
// To sum up, we have:
|
139 |
+
// If document root is unresolvable while canUseDocRootForRelPaths() succeeded, then the image roots will all begin with
|
140 |
+
// the unresolved path.
|
141 |
+
// In this method, if $useDocRootForStructuringCacheDir is true, then it is assumed that canUseDocRootForRelPaths()
|
142 |
+
// succeeded.
|
143 |
+
// OH!
|
144 |
+
// I realize that the image root can be passed as well:
|
145 |
+
// $imageRoot = $webExpressContentDirAbs . '/webp-images';
|
146 |
+
// So the question is: Will $webExpressContentDirAbs also be the unresolved path?
|
147 |
+
// That variable is calculated in WodConfigLoader based on various methods available.
|
148 |
+
// I'm not digging into it, but would expect it to in some cases be resolved. Which means that relative path can not
|
149 |
+
// be found.
|
150 |
+
// So. Lets play it safe and require that document root is resolvable in order to use docRoot for structure
|
151 |
+
|
152 |
+
if (!PathHelper::isDocRootAvailable()) {
|
153 |
+
throw new \Exception(
|
154 |
+
'Can not calculate destination using "doc-root" structure as document root is not available. $_SERVER["DOCUMENT_ROOT"] is empty. ' .
|
155 |
+
'This is probably a misconfiguration on the server. ' .
|
156 |
+
'However, WebP Express can function without using documument root. If you resave options and regenerate the .htaccess files, it should ' .
|
157 |
+
'automatically start to structure the webp files in subfolders that are relative the image root folders rather than document-root.'
|
158 |
+
);
|
159 |
+
}
|
160 |
+
|
161 |
+
if (!PathHelper::isDocRootAvailableAndResolvable()) {
|
162 |
+
throw new \Exception(
|
163 |
+
'Can not calculate destination using "doc-root" structure as document root cannot be resolved for symlinks using "realpath". The ' .
|
164 |
+
'reason for that is probably that open_basedir protection has been set up and that document root is outside outside that open_basedir. ' .
|
165 |
+
'WebP Express can function in that setting, however you will need to resave options and regenerate the .htaccess files. It should then ' .
|
166 |
+
'automatically stop to structure the webp files as relative to document root and instead structure them as relative to image root folders.'
|
167 |
+
);
|
168 |
+
}
|
169 |
+
$docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
|
170 |
+
$imageRoot = $webExpressContentDirAbs . '/webp-images';
|
171 |
+
|
172 |
+
// TODO: make this check work with symlinks
|
173 |
+
//SanityCheck::absPathIsInDocRoot($imageRoot);
|
174 |
+
|
175 |
+
$sourceRel = substr(realpath($source), strlen($docRoot) + 1);
|
176 |
+
$destination = $imageRoot . '/doc-root/' . $sourceRel;
|
177 |
+
$destination = self::appendOrSetExtension($destination, $destinationFolder, $destinationExt, false);
|
178 |
|
|
|
|
|
179 |
|
180 |
+
// TODO: make this check work with symlinks
|
181 |
+
//$destination = SanityCheck::absPathIsInDocRoot($destination);
|
182 |
+
} else {
|
183 |
+
$destination = '';
|
184 |
+
|
185 |
+
$sourceResolved = realpath($source);
|
186 |
+
|
187 |
+
|
188 |
+
// Check roots until we (hopefully) get a match.
|
189 |
+
// (that is: find a root which the source is inside)
|
190 |
+
foreach ($imageRoots->getArray() as $i => $imageRoot) {
|
191 |
+
// in $obj, "rel-path" is only set when document root can be used for relative paths.
|
192 |
+
// So, if it is set, we can use it (beware: we cannot neccessarily use realpath on document root,
|
193 |
+
// but we do not need to - see the long comment in Paths::canUseDocRootForRelPaths())
|
194 |
+
|
195 |
+
$rootPath = $imageRoot->getAbsPath();
|
196 |
+
/*
|
197 |
+
if (isset($obj['rel-path'])) {
|
198 |
+
$docRoot = rtrim($_SERVER["DOCUMENT_ROOT"], '/');
|
199 |
+
$rootPath = $docRoot . '/' . $obj['rel-path'];
|
200 |
+
} else {
|
201 |
+
// If "rel-path" isn't set, then abs-path is, and we can use that.
|
202 |
+
$rootPath = $obj['abs-path'];
|
203 |
+
}*/
|
204 |
+
|
205 |
+
// $source may be resolved or not. Same goes for $rootPath.
|
206 |
+
// We can assume that $rootPath is resolvable using realpath (it ought to exist and be within open_basedir for WP to function)
|
207 |
+
// We can also assume that $source is resolvable (it ought to exist and within open_basedir)
|
208 |
+
// So: Resolve both! and test if the resolved source begins with the resolved rootPath.
|
209 |
+
if (strpos($sourceResolved, realpath($rootPath)) !== false) {
|
210 |
+
$relPath = substr($sourceResolved, strlen(realpath($rootPath)) + 1);
|
211 |
+
$relPath = self::appendOrSetExtension($relPath, $destinationFolder, $destinationExt, false);
|
212 |
+
|
213 |
+
$destination = $webExpressContentDirAbs . '/webp-images/' . $imageRoot->id . '/' . $relPath;
|
214 |
+
break;
|
215 |
+
}
|
216 |
+
}
|
217 |
+
if ($destination == '') {
|
218 |
+
return false;
|
219 |
+
}
|
220 |
+
}
|
221 |
}
|
222 |
|
223 |
} catch (SanityException $e) {
|
235 |
* Returns false if source file is not found or if a path is not sane. Otherwise returns path to source
|
236 |
* destination does not have to exist.
|
237 |
*
|
238 |
+
* @param string $destination Path to destination file (does not have to exist)
|
239 |
+
* @param string $destinationStructure "doc-root" or "image-roots"
|
240 |
+
* @param string $webExpressContentDirAbs
|
241 |
+
* @param ImageRoots $imageRoots An image roots object
|
242 |
*
|
243 |
* @return string|false Returns path to source, if found. If not - or a path is not sane, false is returned
|
244 |
*/
|
245 |
+
private static function findSourceSeparate($destination, $destinationStructure, $webExpressContentDirAbs, $imageRoots)
|
246 |
{
|
247 |
try {
|
248 |
|
249 |
+
if ($destinationStructure == 'doc-root') {
|
|
|
|
|
250 |
|
251 |
+
// Check that destination path is sane and inside document root
|
252 |
+
// --------------------------
|
253 |
+
$destination = SanityCheck::absPathIsInDocRoot($destination);
|
254 |
|
|
|
|
|
|
|
255 |
|
256 |
+
// Check that calculated image root is sane and inside document root
|
257 |
+
// --------------------------
|
258 |
+
$imageRoot = SanityCheck::absPathIsInDocRoot($webExpressContentDirAbs . '/webp-images/doc-root');
|
259 |
|
|
|
|
|
260 |
|
261 |
+
// Calculate source and check that it is sane and exists
|
262 |
+
// -----------------------------------------------------
|
263 |
+
|
264 |
+
// TODO: This does not work on Windows yet.
|
265 |
+
if (strpos($destination, $imageRoot . '/') === 0) {
|
266 |
|
267 |
+
// "Eat" the left part off the $destination parameter. $destination is for example:
|
268 |
+
// "/var/www/webp-express-tests/we0/wp-content-moved/webp-express/webp-images/doc-root/wordpress/uploads-moved/2018/12/tegning5-300x265.jpg.webp"
|
269 |
+
// We also eat the slash (+1)
|
270 |
+
$sourceRel = substr($destination, strlen($imageRoot) + 1);
|
271 |
|
272 |
+
$docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
|
273 |
+
$source = $docRoot . '/' . $sourceRel;
|
274 |
+
$source = preg_replace('/\\.(webp)$/', '', $source);
|
275 |
+
} else {
|
276 |
+
// Try with symlinks resolved
|
277 |
+
// This is not trivial as this must also work when the destination path doesn't exist, and
|
278 |
+
// realpath can only be used to resolve symlinks for files that exists.
|
279 |
+
// But here is how we achieve it anyway:
|
280 |
+
//
|
281 |
+
// 1. We make sure imageRoot exists (if not, create it) - this ensures that we can resolve it.
|
282 |
+
// 2. Find closest folder existing folder (resolved) of destination - using PathHelper::findClosestExistingFolderSymLinksExpanded()
|
283 |
+
// 3. Test that resolved closest existing folder starts with resolved imageRoot
|
284 |
+
// 4. If it does, we could create a dummy file at the destination to get its real path, but we want to avoid that, so instead
|
285 |
+
// we can create the containing directory.
|
286 |
+
// 5. We can now use realpath to get the resolved path of the containing directory. The rest is simple enough.
|
287 |
+
if (!file_exists($imageRoot)) {
|
288 |
+
mkdir($imageRoot, 0777, true);
|
289 |
+
}
|
290 |
+
$closestExistingResolved = PathHelper::findClosestExistingFolderSymLinksExpanded($destination);
|
291 |
+
if ($closestExistingResolved == '') {
|
292 |
+
return false;
|
293 |
+
} else {
|
294 |
+
$imageRootResolved = realpath($imageRoot);
|
295 |
+
if (strpos($closestExistingResolved . '/', $imageRootResolved . '/') === 0) {
|
296 |
+
// echo $destination . '<br>' . $closestExistingResolved . '<br>' . $imageRootResolved . '/'; exit;
|
297 |
+
// Create containing dir for destination
|
298 |
+
$containingDir = PathHelper::dirname($destination);
|
299 |
+
if (!file_exists($containingDir)) {
|
300 |
+
mkdir($containingDir, 0777, true);
|
301 |
+
}
|
302 |
+
$containingDirResolved = realpath($containingDir);
|
303 |
+
|
304 |
+
$filename = PathHelper::basename($destination);
|
305 |
+
$destinationResolved = $containingDirResolved . '/' . $filename;
|
306 |
+
|
307 |
+
$sourceRel = substr($destinationResolved, strlen($imageRootResolved) + 1);
|
308 |
+
|
309 |
+
$docRoot = rtrim(realpath($_SERVER["DOCUMENT_ROOT"]), '/');
|
310 |
+
$source = $docRoot . '/' . $sourceRel;
|
311 |
+
$source = preg_replace('/\\.(webp)$/', '', $source);
|
312 |
+
return $source;
|
313 |
+
} else {
|
314 |
+
return false;
|
315 |
+
}
|
316 |
+
}
|
317 |
+
}
|
318 |
+
|
319 |
+
return SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
320 |
} else {
|
321 |
+
|
322 |
+
// Mission: To find source corresponding to destination (separate) - using the "image-roots" structure.
|
323 |
+
|
324 |
+
// How can we do that?
|
325 |
+
// We got the destination (unresolved) - ie '/website-symlinked/wp-content/webp-express/webp-images/uploads/2018/07/hello.jpg.webp'
|
326 |
+
// If we were lazy and unprecise, we could simply:
|
327 |
+
// - search for "webp-express/webp-images/"
|
328 |
+
// - strip anything before that - result: 'uploads/2018/07/hello.jpg.webp'
|
329 |
+
// - the first path component is the root id.
|
330 |
+
// - the rest of the path is the relative path to the source - if we strip the ".webp" ending
|
331 |
+
|
332 |
+
// So, are we lazy? - what is the alternative?
|
333 |
+
// - Get closest existing resolved folder of destination (ie "/var/www/website/wp-content-moved/webp-express/webp-images/wp-content")
|
334 |
+
// - Check if that folder is below the cache root (resolved) (cache root is the "wp-content" image root + 'webp-express/webp-images')
|
335 |
+
// - Create dir for destination (if missing)
|
336 |
+
// - We can now resolve destination. With cache root also being resolved, we can get the relative dir.
|
337 |
+
// ie 'uploads/2018/07/hello.jpg.webp'.
|
338 |
+
// The first path component is the root id, the rest is the relative path to the source.
|
339 |
+
|
340 |
+
$closestExistingResolved = PathHelper::findClosestExistingFolderSymLinksExpanded($destination);
|
341 |
+
$cacheRoot = $webExpressContentDirAbs . '/webp-images';
|
342 |
+
if ($closestExistingResolved == '') {
|
343 |
+
return false;
|
344 |
+
} else {
|
345 |
+
$cacheRootResolved = realpath($cacheRoot);
|
346 |
+
if (strpos($closestExistingResolved . '/', $cacheRootResolved . '/') === 0) {
|
347 |
+
|
348 |
+
// Create containing dir for destination
|
349 |
+
$containingDir = PathHelper::dirname($destination);
|
350 |
+
if (!file_exists($containingDir)) {
|
351 |
+
mkdir($containingDir, 0777, true);
|
352 |
+
}
|
353 |
+
$containingDirResolved = realpath($containingDir);
|
354 |
+
|
355 |
+
$filename = PathHelper::basename($destination);
|
356 |
+
$destinationResolved = $containingDirResolved . '/' . $filename;
|
357 |
+
$destinationRelToCacheRoot = substr($destinationResolved, strlen($cacheRootResolved) + 1);
|
358 |
+
|
359 |
+
$parts = explode('/', $destinationRelToCacheRoot);
|
360 |
+
$imageRoot = array_shift($parts);
|
361 |
+
$sourceRel = implode('/', $parts);
|
362 |
+
|
363 |
+
$source = $imageRoots->byId($imageRoot)->getAbsPath() . '/' . $sourceRel;
|
364 |
+
$source = preg_replace('/\\.(webp)$/', '', $source);
|
365 |
+
return $source;
|
366 |
+
} else {
|
367 |
+
return false;
|
368 |
+
}
|
369 |
+
}
|
370 |
return false;
|
371 |
}
|
|
|
|
|
|
|
372 |
} catch (SanityException $e) {
|
373 |
return false;
|
374 |
}
|
380 |
* Find source corresponding to destination (mingled)
|
381 |
* Returns false if not found. Otherwise returns path to source
|
382 |
*
|
383 |
+
* @param string $destination Path to destination file (does not have to exist)
|
384 |
+
* @param string $destinationExt Extension ('append' or 'set')
|
385 |
+
* @param string $destinationStructure "doc-root" or "image-roots"
|
386 |
*
|
387 |
* @return string|false Returns path to source, if found. If not - or a path is not sane, false is returned
|
388 |
*/
|
389 |
+
private static function findSourceMingled($destination, $destinationExt, $destinationStructure)
|
390 |
{
|
391 |
try {
|
392 |
|
393 |
+
if ($destinationStructure == 'doc-root') {
|
394 |
+
// Check that destination path is sane and inside document root
|
395 |
+
// --------------------------
|
396 |
+
$destination = SanityCheck::absPathIsInDocRoot($destination);
|
397 |
+
} else {
|
398 |
+
// The following will fail if path contains directory traversal. TODO: Is that ok?
|
399 |
+
$destination = SanityCheck::absPath($destination);
|
400 |
+
}
|
401 |
|
402 |
// Calculate source and check that it is sane and exists
|
403 |
// -----------------------------------------------------
|
412 |
$source = preg_replace('/\\.webp$/', '.png', $destination);
|
413 |
}
|
414 |
}
|
415 |
+
if ($destinationStructure == 'doc-root') {
|
416 |
+
$source = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
417 |
+
} else {
|
418 |
+
$source = SanityCheck::absPathExistsAndIsFile($source);
|
419 |
+
}
|
420 |
|
421 |
|
422 |
} catch (SanityException $e) {
|
430 |
* Get source from destination (and some configurations)
|
431 |
* Returns false if not found. Otherwise returns path to source
|
432 |
*
|
433 |
+
* @param string $destination Path to destination file (does not have to exist). May not contain directory traversal
|
434 |
* @param string $destinationFolder 'mingled' or 'separate'
|
435 |
* @param string $destinationExt Extension ('append' or 'set')
|
436 |
+
* @param string $destinationStructure "doc-root" or "image-roots"
|
437 |
* @param string $webExpressContentDirAbs
|
438 |
+
* @param ImageRoots $imageRoots An image roots object
|
439 |
*
|
440 |
* @return string|false Returns path to source, if found. If not - or a path is not sane, false is returned
|
441 |
*/
|
442 |
+
public static function findSource($destination, $destinationFolder, $destinationExt, $destinationStructure, $webExpressContentDirAbs, $imageRoots)
|
443 |
{
|
444 |
+
|
445 |
try {
|
446 |
|
447 |
+
if ($destinationStructure == 'doc-root') {
|
448 |
+
// Check that destination path is sane and inside document root
|
449 |
+
// --------------------------
|
450 |
+
$destination = SanityCheck::absPathIsInDocRoot($destination);
|
451 |
+
} else {
|
452 |
+
// The following will fail if path contains directory traversal. TODO: Is that ok?
|
453 |
+
$destination = SanityCheck::absPath($destination);
|
454 |
+
}
|
455 |
|
456 |
} catch (SanityException $e) {
|
457 |
return false;
|
458 |
}
|
459 |
|
460 |
if ($destinationFolder == 'mingled') {
|
461 |
+
$result = self::findSourceMingled($destination, $destinationExt, $destinationStructure);
|
462 |
if ($result === false) {
|
463 |
+
$result = self::findSourceSeparate($destination, $destinationStructure, $webExpressContentDirAbs, $imageRoots);
|
464 |
}
|
465 |
return $result;
|
466 |
} else {
|
467 |
+
return self::findSourceSeparate($destination, $destinationStructure, $webExpressContentDirAbs, $imageRoots);
|
468 |
}
|
469 |
}
|
470 |
|
558 |
|
559 |
$text = preg_replace('#' . preg_quote($_SERVER["DOCUMENT_ROOT"]) . '#', '[doc-root]', $text);
|
560 |
|
561 |
+
$text = 'WebP Express 0.15.0. ' . $msgTop . ', ' . date("Y-m-d H:i:s") . "\n\r\n\r" . $text;
|
562 |
|
563 |
$logFile = self::getLogFilename($source, $logDir);
|
564 |
|
580 |
*
|
581 |
* PS: To convert with a specific converter, set it in the $converter param.
|
582 |
*
|
583 |
+
* @param string $source Full path to the source file that was converted.
|
584 |
+
* @param string $destination Full path to the destination file (may exist or not).
|
585 |
* @param array $convertOptions Conversion options.
|
586 |
* @param string $logDir The folder where log files are kept.
|
587 |
* @param string $converter (optional) Set it to convert with a specific converter.
|
668 |
// ---------------------------------------------
|
669 |
try {
|
670 |
|
671 |
+
// Check that source path is sane, exists, is a file.
|
672 |
// -------------------------------------------------------
|
673 |
+
//$source = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
674 |
+
$source = SanityCheck::absPathExistsAndIsFile($source);
|
675 |
|
676 |
|
677 |
+
// Check that destination path is sane
|
678 |
// -------------------------------------------------------
|
679 |
+
//$destination = SanityCheck::absPathIsInDocRoot($destination);
|
680 |
+
$destination = SanityCheck::absPath($destination);
|
681 |
$destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Destination does not end with .webp');
|
682 |
|
683 |
|
684 |
+
// Check that log path is sane
|
685 |
// -------------------------------------------------------
|
686 |
+
//$logDir = SanityCheck::absPathIsInDocRoot($logDir);
|
687 |
+
$logDir = SanityCheck::absPath($logDir);
|
688 |
|
689 |
|
690 |
// PS: No need to check $logMsgTop. Log files are markdown and stored as ".md". They can do no harm.
|
lib/classes/FileHelper.php
CHANGED
@@ -301,6 +301,37 @@ class FileHelper
|
|
301 |
return $success;
|
302 |
}
|
303 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
|
305 |
/**
|
306 |
* Verify if OS is Windows
|
301 |
return $success;
|
302 |
}
|
303 |
|
304 |
+
/**
|
305 |
+
* Remove empty subfolders.
|
306 |
+
*
|
307 |
+
* Got it here: https://stackoverflow.com/a/1833681/842756
|
308 |
+
*
|
309 |
+
* @return boolean If folder is (was) empty
|
310 |
+
*/
|
311 |
+
public static function removeEmptySubFolders($path, $removeEmptySelfToo = false)
|
312 |
+
{
|
313 |
+
if (!file_exists($path)) {
|
314 |
+
return;
|
315 |
+
}
|
316 |
+
$empty = true;
|
317 |
+
foreach (scandir($path) as $file) {
|
318 |
+
if (($file == '.') || ($file == '..')) {
|
319 |
+
continue;
|
320 |
+
}
|
321 |
+
$file = $path . DIRECTORY_SEPARATOR . $file;
|
322 |
+
if (is_dir($file)) {
|
323 |
+
if (!self::removeEmptySubFolders($file, true)) {
|
324 |
+
$empty=false;
|
325 |
+
}
|
326 |
+
} else {
|
327 |
+
$empty=false;
|
328 |
+
}
|
329 |
+
}
|
330 |
+
if ($empty && $removeEmptySelfToo) {
|
331 |
+
rmdir($path);
|
332 |
+
}
|
333 |
+
return $empty;
|
334 |
+
}
|
335 |
|
336 |
/**
|
337 |
* Verify if OS is Windows
|
lib/classes/HTAccess.php
CHANGED
@@ -4,470 +4,12 @@ namespace WebPExpress;
|
|
4 |
|
5 |
use \WebPExpress\Config;
|
6 |
use \WebPExpress\FileHelper;
|
|
|
7 |
use \WebPExpress\Paths;
|
8 |
use \WebPExpress\State;
|
9 |
|
10 |
class HTAccess
|
11 |
{
|
12 |
-
// (called from this file only. BUT our saveRules methods calls it, and it is called from several classes)
|
13 |
-
public static function generateHTAccessRulesFromConfigObj($config, $htaccessDir = 'index')
|
14 |
-
{
|
15 |
-
// Any option that is newer than ~v.0.2 may not be set yet.
|
16 |
-
// So, in order to not have to use isset() all over the place, set to values
|
17 |
-
// that results in same behaviour as before the option was introduced.
|
18 |
-
// Beware that this may not be same as the default value in the UI (but it is generally)
|
19 |
-
|
20 |
-
// TODO: can we use the new fix method instead?
|
21 |
-
|
22 |
-
$defaults = [
|
23 |
-
'enable-redirection-to-converter' => true,
|
24 |
-
'forward-query-string' => true,
|
25 |
-
'image-types' => 1,
|
26 |
-
'do-not-pass-source-in-query-string' => false,
|
27 |
-
'redirect-to-existing-in-htaccess' => false,
|
28 |
-
'only-redirect-to-converter-on-cache-miss' => false,
|
29 |
-
'destination-folder' => 'separate',
|
30 |
-
'destination-extension' => 'append',
|
31 |
-
'success-response' => 'converted',
|
32 |
-
];
|
33 |
-
$config = array_merge($defaults, $config);
|
34 |
-
|
35 |
-
if ((!$config['enable-redirection-to-converter']) && (!$config['redirect-to-existing-in-htaccess']) && (!$config['enable-redirection-to-webp-realizer'])) {
|
36 |
-
return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor to existing webp, and the "convert non-existing webp-files upon request" option has not been enabled)';
|
37 |
-
}
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
if (isset($config['base-htaccess-on-these-capability-tests'])) {
|
42 |
-
$capTests = $config['base-htaccess-on-these-capability-tests'];
|
43 |
-
$modHeaderDefinitelyUnavailable = ($capTests['modHeaderWorking'] === false);
|
44 |
-
$passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
|
45 |
-
$passThroughHeaderDefinitelyAavailable = ($capTests['passThroughHeaderWorking'] === true);
|
46 |
-
$passThrougEnvVarDefinitelyUnavailable = ($capTests['passThroughEnvWorking'] === false);
|
47 |
-
$passThrougEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
|
48 |
-
} else {
|
49 |
-
$modHeaderDefinitelyUnavailable = false;
|
50 |
-
$passThroughHeaderDefinitelyUnavailable = false;
|
51 |
-
$passThroughHeaderDefinitelyAavailable = false;
|
52 |
-
$passThrougEnvVarDefinitelyUnavailable = false;
|
53 |
-
$passThrougEnvVarDefinitelyAvailable = false;
|
54 |
-
}
|
55 |
-
|
56 |
-
$setEnvVar = !$passThrougEnvVarDefinitelyUnavailable;
|
57 |
-
$passFullFilePathInQS = false;
|
58 |
-
$passRelativeFilePathInQS = !($passThrougEnvVarDefinitelyAvailable || $passThroughHeaderDefinitelyAavailable);
|
59 |
-
$passFullFilePathInQSRealizer = false;
|
60 |
-
$passRelativeFilePathInQSRealizer = $passRelativeFilePathInQS;
|
61 |
-
|
62 |
-
|
63 |
-
$addVary = $config['redirect-to-existing-in-htaccess'];
|
64 |
-
if ($modHeaderDefinitelyUnavailable) {
|
65 |
-
$addVary = false;
|
66 |
-
}
|
67 |
-
|
68 |
-
|
69 |
-
/* Calculate $fileExt */
|
70 |
-
$imageTypes = $config['image-types'];
|
71 |
-
$fileExtensions = [];
|
72 |
-
if ($imageTypes & 1) {
|
73 |
-
$fileExtensions[] = 'jpe?g';
|
74 |
-
}
|
75 |
-
if ($imageTypes & 2) {
|
76 |
-
$fileExtensions[] = 'png';
|
77 |
-
}
|
78 |
-
$fileExt = implode('|', $fileExtensions);
|
79 |
-
|
80 |
-
if ($imageTypes == 0) {
|
81 |
-
return '# WebP Express disabled (no image types have been choosen to be converted/redirected)';
|
82 |
-
}
|
83 |
-
|
84 |
-
|
85 |
-
// Build cache control rules
|
86 |
-
$ccRules = '';
|
87 |
-
$cacheControlHeader = Config::getCacheControlHeader($config);
|
88 |
-
if ($cacheControlHeader != '') {
|
89 |
-
|
90 |
-
if ($config['redirect-to-existing-in-htaccess']) {
|
91 |
-
$ccRules .= " # Set Cache-Control header so these direct redirections also get the header set\n";
|
92 |
-
if ($config['enable-redirection-to-webp-realizer']) {
|
93 |
-
$ccRules .= " # (and also webp-realizer.php)\n";
|
94 |
-
}
|
95 |
-
} else {
|
96 |
-
if ($config['enable-redirection-to-webp-realizer']) {
|
97 |
-
$ccRules .= " # Set Cache-Control header for requests to webp images\n";
|
98 |
-
}
|
99 |
-
}
|
100 |
-
$ccRules .= " <IfModule mod_headers.c>\n";
|
101 |
-
$ccRules .= " <FilesMatch \"\.webp$\">\n";
|
102 |
-
$ccRules .= " Header set Cache-Control \"" . $cacheControlHeader . "\"\n";
|
103 |
-
$ccRules .= " </FilesMatch>\n";
|
104 |
-
$ccRules .= " </IfModule>\n\n";
|
105 |
-
|
106 |
-
// Fall back to mod_expires if mod_headers is unavailable
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
if ($modHeaderDefinitelyUnavailable) {
|
111 |
-
$cacheControl = $config['cache-control'];
|
112 |
-
|
113 |
-
if ($cacheControl == 'custom') {
|
114 |
-
$expires = '';
|
115 |
-
|
116 |
-
// Do not add Expire header if private is set
|
117 |
-
// - because then the user don't want caching in proxies / CDNs.
|
118 |
-
// the Expires header doesn't differentiate between private/public
|
119 |
-
if (!(preg_match('/private/', $config['cache-control-custom']))) {
|
120 |
-
if (preg_match('/max-age=(\d+)/', $config['cache-control-custom'], $matches)) {
|
121 |
-
if (isset($matches[1])) {
|
122 |
-
$expires = $matches[1] . ' seconds';
|
123 |
-
}
|
124 |
-
}
|
125 |
-
}
|
126 |
-
|
127 |
-
} elseif ($cacheControl == 'no-header') {
|
128 |
-
$expires = '';
|
129 |
-
} elseif ($cacheControl == 'set') {
|
130 |
-
if ($config['cache-control-public']) {
|
131 |
-
$cacheControlOptions = [
|
132 |
-
'no-header' => '',
|
133 |
-
'one-second' => '1 seconds',
|
134 |
-
'one-minute' => '1 minutes',
|
135 |
-
'one-hour' => '1 hours',
|
136 |
-
'one-day' => '1 days',
|
137 |
-
'one-week' => '1 weeks',
|
138 |
-
'one-month' => '1 months',
|
139 |
-
'one-year' => '1 years',
|
140 |
-
];
|
141 |
-
$expires = $cacheControlOptions[$config['cache-control-max-age']];
|
142 |
-
}
|
143 |
-
}
|
144 |
-
|
145 |
-
if ($expires != '') {
|
146 |
-
// in case mod_headers is missing, try mod_expires
|
147 |
-
$ccRules .= " # Fall back to mod_expires if mod_headers is unavailable\n";
|
148 |
-
$ccRules .= " <IfModule !mod_headers.c>\n";
|
149 |
-
$ccRules .= " <IfModule mod_expires.c>\n";
|
150 |
-
$ccRules .= " ExpiresActive On\n";
|
151 |
-
$ccRules .= " ExpiresByType image/webp \"access plus " . $expires . "\"\n";
|
152 |
-
$ccRules .= " </IfModule>\n";
|
153 |
-
$ccRules .= " </IfModule>\n\n";
|
154 |
-
}
|
155 |
-
}
|
156 |
-
}
|
157 |
-
|
158 |
-
|
159 |
-
/* Build rules */
|
160 |
-
|
161 |
-
/* When .htaccess is placed in root (index), the rules needs to be confined only to work in
|
162 |
-
content folder (if uploads folder is moved, perhaps also that)
|
163 |
-
Rules needs to start with ie "^/?(wp-content/.+)" rather than "^/?(.+)"
|
164 |
-
In the case that upload folder is in root too, rules needs to apply to both.
|
165 |
-
We do it like this: "^/?((?:wp-content|uploads)/.+)" (using non capturing group)
|
166 |
-
*/
|
167 |
-
|
168 |
-
$rewriteRuleStart = '^/?(.+)';
|
169 |
-
if ($htaccessDir == 'index') {
|
170 |
-
// Get relative path between index dir and wp-content dir / uploads
|
171 |
-
// Because we want to restrict the rule so it doesn't work on wp-admin, but only those two.
|
172 |
-
|
173 |
-
$wpContentRel = PathHelper::getRelDir(Paths::getIndexDirAbs(), Paths::getContentDirAbs());
|
174 |
-
$uploadsRel = PathHelper::getRelDir(Paths::getIndexDirAbs(), Paths::getUploadDirAbs());
|
175 |
-
|
176 |
-
//$rules .= '# rel: ' . $uploadsRel . "\n";
|
177 |
-
|
178 |
-
if (strpos($wpContentRel, '.') !== 0) {
|
179 |
-
|
180 |
-
if (strpos($uploadsRel, $wpContentRel) === 0) {
|
181 |
-
$rewriteRuleStart = '^/?(' . $wpContentRel . '/.+)';
|
182 |
-
} else {
|
183 |
-
$rewriteRuleStart = '^/?((?:' . $wpContentRel . '|' . $uploadsRel . '/.+)';
|
184 |
-
}
|
185 |
-
}
|
186 |
-
}
|
187 |
-
|
188 |
-
$rules = '';
|
189 |
-
|
190 |
-
|
191 |
-
/*
|
192 |
-
// The next line sets an environment variable.
|
193 |
-
// On the options page, we verify if this is set to diagnose if "AllowOverride None" is presented in 'httpd.conf'
|
194 |
-
//$rules .= "# The following SetEnv allows to diagnose if .htaccess files are turned off\n";
|
195 |
-
//$rules .= "SetEnv HTACCESS on\n\n";
|
196 |
-
*/
|
197 |
-
$rules .= "# The rules below are a result of the WebP Express options, Wordpress configuration and the following .htaccess capability tests:\n" .
|
198 |
-
"# - mod_header working?: " . ($capTests['modHeaderWorking'] === true ? 'yes' : ($capTests['modHeaderWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
|
199 |
-
"# - pass variable from .htaccess to script through header working?: " . ($capTests['passThroughHeaderWorking'] === true ? 'yes' : ($capTests['passThroughHeaderWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
|
200 |
-
"# - pass variable from .htaccess to script through environment variable working?: " . ($capTests['passThroughEnvWorking'] === true ? 'yes' : ($capTests['passThroughEnvWorking'] === false ? 'no' : 'could not be determined')) . "\n";
|
201 |
-
|
202 |
-
$rules .= "<IfModule mod_rewrite.c>\n" .
|
203 |
-
" RewriteEngine On\n\n";
|
204 |
-
|
205 |
-
$cacheDirRel = Paths::getCacheDirRel() . '/doc-root';
|
206 |
-
|
207 |
-
$htaccessDirRel = '';
|
208 |
-
switch ($htaccessDir) {
|
209 |
-
case 'index':
|
210 |
-
$htaccessDirRel = Paths::getIndexDirRel();
|
211 |
-
break;
|
212 |
-
case 'home':
|
213 |
-
$htaccessDirRel = Paths::getHomeDirRel();
|
214 |
-
break;
|
215 |
-
case 'plugin':
|
216 |
-
$htaccessDirRel = Paths::getPluginDirRel();
|
217 |
-
break;
|
218 |
-
case 'uploads':
|
219 |
-
$htaccessDirRel = Paths::getUploadDirRel();
|
220 |
-
break;
|
221 |
-
case 'wp-content':
|
222 |
-
$htaccessDirRel = Paths::getContentDirRel();
|
223 |
-
break;
|
224 |
-
}
|
225 |
-
|
226 |
-
|
227 |
-
// TODO: Is it possible to handle when wp-content is outside document root?
|
228 |
-
|
229 |
-
// TODO: It seems $pathToExisting needs to be adjusted, depending on where the .htaccess is located
|
230 |
-
// Ie, if plugin folder has been moved out of ABSPATH, we should ie set
|
231 |
-
// $pathToExisting to 'doc-root/plugins-moved/'
|
232 |
-
// to get: RewriteRule ^\/?(.*)\.(jpe?g)$ /wp-content-moved/webp-express/webp-images/doc-root/plugins-moved/$1.$2.webp [NC,T=image/webp,E=WEBPACCEPT:1,E=EXISTING:1,L]
|
233 |
-
|
234 |
-
// https://stackoverflow.com/questions/34124819/mod-rewrite-set-custom-header-through-htaccess
|
235 |
-
$mingled = ($config['destination-folder'] == 'mingled');
|
236 |
-
|
237 |
-
if ($config['redirect-to-existing-in-htaccess']) {
|
238 |
-
if ($mingled) {
|
239 |
-
$rules .= " # Redirect to existing converted image in same dir (if browser supports webp)\n";
|
240 |
-
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
|
241 |
-
|
242 |
-
if ($config['destination-extension'] == 'append') {
|
243 |
-
$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.$2.webp -f\n";
|
244 |
-
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ $1.$2.webp [T=image/webp,E=EXISTING:1," . ($addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
245 |
-
} else {
|
246 |
-
$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp -f\n";
|
247 |
-
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1," . ($addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
248 |
-
//$rules .= " RewriteRule ^(.+)\.(" . $fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1,L]\n\n";
|
249 |
-
}
|
250 |
-
}
|
251 |
-
|
252 |
-
$rules .= " # Redirect to existing converted image in cache-dir (if browser supports webp)\n";
|
253 |
-
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
|
254 |
-
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
255 |
-
$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp -f\n";
|
256 |
-
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
|
257 |
-
//$rules .= " RewriteRule ^\/?(.*)\.(" . $fileExt . ")$ /" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
|
258 |
-
|
259 |
-
if ($addVary) {
|
260 |
-
$rules .= " # Make sure that browsers which does not support webp also gets the Vary:Accept header\n" .
|
261 |
-
" # when requesting images that would be redirected to existing webp on browsers that does.\n" .
|
262 |
-
" <IfModule mod_setenvif.c>\n" .
|
263 |
-
" SetEnvIf Request_URI \"\.(" . $fileExt . ")$\" ADDVARY\n" .
|
264 |
-
" </IfModule>\n\n";
|
265 |
-
}
|
266 |
-
|
267 |
-
$rules .= $ccRules;
|
268 |
-
|
269 |
-
}
|
270 |
-
|
271 |
-
// Do not add header magic if passing through env is definitely working
|
272 |
-
// Do not add either, if we definitily know it isn't working
|
273 |
-
if ((!$passThrougEnvVarDefinitelyAvailable) && (!$passThroughHeaderDefinitelyUnavailable)) {
|
274 |
-
if ($config['enable-redirection-to-converter']) {
|
275 |
-
$rules .= " # Pass REQUEST_FILENAME to webp-on-demand.php in request header\n";
|
276 |
-
//$rules .= $basicConditions;
|
277 |
-
//$rules .= " RewriteRule ^(.*)\.(" . $fileExt . ")$ - [E=REQFN:%{REQUEST_FILENAME}]\n" .
|
278 |
-
$rules .= " <IfModule mod_headers.c>\n" .
|
279 |
-
" RequestHeader set REQFN \"%{REQFN}e\" env=REQFN\n" .
|
280 |
-
" </IfModule>\n\n";
|
281 |
-
|
282 |
-
}
|
283 |
-
if ($config['enable-redirection-to-webp-realizer']) {
|
284 |
-
// We haven't implemented a clever way to pass through header for webp-realizer yet
|
285 |
-
}
|
286 |
-
}
|
287 |
-
|
288 |
-
if ($config['enable-redirection-to-webp-realizer']) {
|
289 |
-
/*
|
290 |
-
# Pass REQUEST_FILENAME to PHP in request header
|
291 |
-
RewriteCond %{REQUEST_FILENAME} !-f
|
292 |
-
RewriteCond %{DOCUMENT_ROOT}/wordpress/uploads-moved/$1 -f
|
293 |
-
RewriteRule ^(.*)\.(webp)$ - [E=REQFN:%{REQUEST_FILENAME}]
|
294 |
-
<IfModule mod_headers.c>
|
295 |
-
RequestHeader set REQFN "%{REQFN}e" env=REQFN
|
296 |
-
</IfModule>
|
297 |
-
|
298 |
-
# WebP Realizer: Redirect non-existing webp images to converter when a corresponding jpeg/png is found
|
299 |
-
RewriteCond %{REQUEST_FILENAME} !-f
|
300 |
-
RewriteCond %{DOCUMENT_ROOT}/wordpress/uploads-moved/$1 -f
|
301 |
-
RewriteRule ^(.*)\.(webp)$ /plugins-moved/webp-express/wod/webp-realizer.php?wp-content=wp-content-moved [NC,L]
|
302 |
-
*/
|
303 |
-
|
304 |
-
$basicConditionsRealizer = '';
|
305 |
-
$basicConditionsRealizer .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
|
306 |
-
if ($mingled) {
|
307 |
-
if ($config['destination-extension'] == 'append') {
|
308 |
-
$basicConditionsRealizer .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1 -f\n";
|
309 |
-
} else {
|
310 |
-
//$basicConditionsRealizer .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp !-f\n";
|
311 |
-
}
|
312 |
-
} else {
|
313 |
-
//$basicConditionsRealizer .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp !-f\n";
|
314 |
-
}
|
315 |
-
|
316 |
-
$rules .= " # WebP Realizer: Redirect non-existing webp images to webp-realizer.php, which will locate corresponding jpg/png, convert it, and deliver the webp (if possible) \n";
|
317 |
-
$rules .= $basicConditionsRealizer;
|
318 |
-
|
319 |
-
/*
|
320 |
-
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(webp)$ " .
|
321 |
-
"/" . Paths::getWebPRealizerUrlPath() .
|
322 |
-
($passFullFilePathInQS ? "?xdestination=x%{SCRIPT_FILENAME}&" : "?") .
|
323 |
-
"wp-content=" . Paths::getContentDirRel() .
|
324 |
-
" [" . ($setEnvVar ? ('E=REQFN:%{REQUEST_FILENAME}' . ','): '') . "NC,L]\n\n"; // E=WOD:1
|
325 |
-
*/
|
326 |
-
$params = [];
|
327 |
-
if ($passFullFilePathInQSRealizer) {
|
328 |
-
$params[] = 'xdestination=x%{SCRIPT_FILENAME}';
|
329 |
-
} elseif ($passRelativeFilePathInQSRealizer) {
|
330 |
-
$params[] = 'xdestination-rel=x' . $htaccessDirRel . '/$1.$2';
|
331 |
-
}
|
332 |
-
if (!$passThrougEnvVarDefinitelyAvailable) {
|
333 |
-
$params[] = "wp-content=" . Paths::getContentDirRel();
|
334 |
-
}
|
335 |
-
|
336 |
-
// TODO: When $rewriteRuleStart is empty, we don't need the .*, do we? - test
|
337 |
-
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(webp)$ " .
|
338 |
-
"/" . Paths::getWebPRealizerUrlPath() .
|
339 |
-
((count($params) > 0) ? "?" . implode('&', $params) : '') .
|
340 |
-
" [" . ($setEnvVar ? ('E=DESTINATIONREL:' . $htaccessDirRel . '/$0' . ','): '') . (!$passThrougEnvVarDefinitelyUnavailable ? 'E=WPCONTENT:' . Paths::getContentDirRel() . ',' : '') . "NC,L]\n\n"; // E=WOD:1
|
341 |
-
|
342 |
-
|
343 |
-
if (!$config['redirect-to-existing-in-htaccess']) {
|
344 |
-
$rules .= $ccRules;
|
345 |
-
}
|
346 |
-
}
|
347 |
-
|
348 |
-
if ($config['enable-redirection-to-converter']) {
|
349 |
-
$basicConditions = '';
|
350 |
-
if ($config['only-redirect-to-converter-for-webp-enabled-browsers']) {
|
351 |
-
$basicConditions = " RewriteCond %{HTTP_ACCEPT} image/webp\n";
|
352 |
-
}
|
353 |
-
$basicConditions .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
354 |
-
if ($config['only-redirect-to-converter-on-cache-miss']) {
|
355 |
-
if ($mingled) {
|
356 |
-
if ($config['destination-extension'] == 'append') {
|
357 |
-
$basicConditions .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.$2.webp !-f\n";
|
358 |
-
} else {
|
359 |
-
$basicConditions .= " RewriteCond %{DOCUMENT_ROOT}/" . $htaccessDirRel . "/$1.webp !-f\n";
|
360 |
-
}
|
361 |
-
} else {
|
362 |
-
$basicConditions .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . $htaccessDirRel . "/$1.$2.webp !-f\n";
|
363 |
-
}
|
364 |
-
}
|
365 |
-
|
366 |
-
$rules .= " # Redirect images to webp-on-demand.php ";
|
367 |
-
if ($config['only-redirect-to-converter-for-webp-enabled-browsers']) {
|
368 |
-
$rules .= "(if browser supports webp)\n";
|
369 |
-
} else {
|
370 |
-
$rules .= "(regardless whether browser supports webp or not!)\n";
|
371 |
-
}
|
372 |
-
if ($config['only-redirect-to-converter-on-cache-miss']) {
|
373 |
-
$rules .= " # - but only, when no existing converted image is found\n";
|
374 |
-
}
|
375 |
-
$rules .= $basicConditions;
|
376 |
-
|
377 |
-
if ($config['forward-query-string']) {
|
378 |
-
$rules .= " RewriteCond %{QUERY_STRING} (.*)\n";
|
379 |
-
}
|
380 |
-
/*
|
381 |
-
if ($config['forward-query-string']) {
|
382 |
-
}*/
|
383 |
-
|
384 |
-
|
385 |
-
// TODO:
|
386 |
-
// Add "NE" flag?
|
387 |
-
// https://github.com/rosell-dk/webp-convert/issues/95
|
388 |
-
// (and try testing spaces in directory paths)
|
389 |
-
|
390 |
-
$params = [];
|
391 |
-
if ($passFullFilePathInQS) {
|
392 |
-
$params[] = 'xsource=x%{SCRIPT_FILENAME}';
|
393 |
-
} elseif ($passRelativeFilePathInQS) {
|
394 |
-
$params[] = 'xsource-rel=x' . $htaccessDirRel . '/$1.$2';
|
395 |
-
}
|
396 |
-
if (!$passThrougEnvVarDefinitelyAvailable) {
|
397 |
-
$params[] = "wp-content=" . Paths::getContentDirRel();
|
398 |
-
}
|
399 |
-
if ($config['forward-query-string']) {
|
400 |
-
$params[] = '%1';
|
401 |
-
}
|
402 |
-
|
403 |
-
// TODO: When $rewriteRuleStart is empty, we don't need the .*, do we? - test
|
404 |
-
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . $fileExt . ")$ " .
|
405 |
-
"/" . Paths::getWodUrlPath() .
|
406 |
-
"?" . implode('&', $params) .
|
407 |
-
" [" . ($setEnvVar ? ('E=REQFN:%{REQUEST_FILENAME},'): '') . (!$passThrougEnvVarDefinitelyUnavailable ? 'E=WPCONTENT:' . Paths::getContentDirRel() . ',' : '') . "NC,L]\n"; // E=WOD:1
|
408 |
-
|
409 |
-
$rules .= "\n";
|
410 |
-
}
|
411 |
-
|
412 |
-
//$addVary = ($config['enable-redirection-to-converter'] && ($config['success-response'] == 'converted')) || ($config['redirect-to-existing-in-htaccess']);
|
413 |
-
|
414 |
-
if ($addVary) {
|
415 |
-
$rules .= " <IfModule mod_headers.c>\n";
|
416 |
-
$rules .= " <IfModule mod_setenvif.c>\n";
|
417 |
-
|
418 |
-
$rules .= " # Apache appends \"REDIRECT_\" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not.\n" .
|
419 |
-
" # So, the next lines are for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
|
420 |
-
" SetEnvIf REDIRECT_EXISTING 1 EXISTING=1\n" .
|
421 |
-
" SetEnvIf REDIRECT_ADDVARY 1 ADDVARY=1\n\n";
|
422 |
-
|
423 |
-
$rules .= " # Set Vary:Accept header for the image types handled by WebP Express.\n" .
|
424 |
-
" # The purpose is to make proxies and CDNs aware that the response varies with the Accept header. \n" .
|
425 |
-
" Header append \"Vary\" \"Accept\" env=ADDVARY\n\n";
|
426 |
-
|
427 |
-
if ($config['redirect-to-existing-in-htaccess']) {
|
428 |
-
$rules .= " # Set X-WebP-Express header for diagnose purposes\n" .
|
429 |
-
//" SetEnvIf REDIRECT_WOD 1 WOD=1\n\n" .
|
430 |
-
//" # Set the debug header\n" .
|
431 |
-
" Header set \"X-WebP-Express\" \"Redirected directly to existing webp\" env=EXISTING\n";
|
432 |
-
//" Header set \"X-WebP-Express\" \"Redirected to image converter\" env=WOD\n" .
|
433 |
-
}
|
434 |
-
$rules .= " </IfModule>\n" .
|
435 |
-
" </IfModule>\n\n";
|
436 |
-
}
|
437 |
-
$rules .="</IfModule>\n";
|
438 |
-
|
439 |
-
/*if ($config['redirect-to-existing-in-htaccess']) {
|
440 |
-
$rules .=
|
441 |
-
"<IfModule mod_headers.c>\n" .
|
442 |
-
" # Append Vary Accept header, when the rules above are redirecting to existing webp\n" .
|
443 |
-
" # or existing jpg" .
|
444 |
-
|
445 |
-
" # Apache appends \"REDIRECT_\" in front of the environment variables, but LiteSpeed does not.\n" .
|
446 |
-
" # These next line is for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
|
447 |
-
" SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1\n\n" .
|
448 |
-
|
449 |
-
" # Make CDN caching possible.\n" .
|
450 |
-
" # The effect is that the CDN will cache both the webp image and the jpeg/png image and return the proper\n" .
|
451 |
-
" # image to the proper clients (for this to work, make sure to set up CDN to forward the \"Accept\" header)\n" .
|
452 |
-
" Header append Vary Accept env=WEBPACCEPT\n" .
|
453 |
-
"</IfModule>\n\n";
|
454 |
-
}*/
|
455 |
-
|
456 |
-
$rules .= "<IfModule mod_mime.c>\n";
|
457 |
-
$rules .= " AddType image/webp .webp\n";
|
458 |
-
$rules .= "</IfModule>\n";
|
459 |
-
return $rules;
|
460 |
-
}
|
461 |
-
|
462 |
-
/* only called from page-messages.inc, but commented out there... */
|
463 |
-
public static function generateHTAccessRulesFromConfigFile($htaccessDir = '') {
|
464 |
-
if (Config::isConfigFileThereAndOk()) {
|
465 |
-
return self::generateHTAccessRulesFromConfigObj(Config::loadConfig(), $htaccessDir);
|
466 |
-
} else {
|
467 |
-
return false;
|
468 |
-
}
|
469 |
-
}
|
470 |
-
|
471 |
public static function arePathsUsedInHTAccessOutdated() {
|
472 |
if (!Config::isConfigFileThere()) {
|
473 |
// this properly means that rewrite rules have never been generated
|
@@ -475,9 +17,7 @@ class HTAccess
|
|
475 |
}
|
476 |
|
477 |
$pathsGoingToBeUsedInHtaccess = [
|
478 |
-
'existing' => Paths::getPathToExisting(),
|
479 |
'wod-url-path' => Paths::getWodUrlPath(),
|
480 |
-
'config-dir-rel' => Paths::getConfigDirRel()
|
481 |
];
|
482 |
|
483 |
$config = Config::loadConfig();
|
@@ -487,8 +27,10 @@ class HTAccess
|
|
487 |
}
|
488 |
|
489 |
foreach ($config['paths-used-in-htaccess'] as $prop => $value) {
|
490 |
-
if ($
|
491 |
-
|
|
|
|
|
492 |
}
|
493 |
}
|
494 |
}
|
@@ -516,13 +58,19 @@ class HTAccess
|
|
516 |
'cache-control-max-age' => 'one-week',
|
517 |
'cache-control-public' => true,
|
518 |
'enable-redirection-to-webp-realizer' => false,
|
519 |
-
'enable-redirection-to-converter' => true
|
|
|
|
|
|
|
|
|
520 |
];
|
521 |
|
|
|
522 |
if (isset($newConfig['redirect-to-existing-in-htaccess']) && $newConfig['redirect-to-existing-in-htaccess']) {
|
523 |
$propsToCompare['destination-folder'] = 'separate';
|
524 |
$propsToCompare['destination-extension'] = 'append';
|
525 |
-
|
|
|
526 |
|
527 |
foreach ($propsToCompare as $prop => $behaviourBeforeIntroduced) {
|
528 |
if (!isset($newConfig[$prop])) {
|
@@ -587,15 +135,13 @@ class HTAccess
|
|
587 |
|
588 |
|
589 |
/**
|
590 |
-
*
|
591 |
-
* This may not be possible (it requires read permission)
|
592 |
-
* Return true, false, or null if we just can't tell
|
593 |
*/
|
594 |
-
public static function
|
595 |
if (FileHelper::fileExists($filename)) {
|
596 |
$content = FileHelper::loadFile($filename);
|
597 |
if ($content === false) {
|
598 |
-
return
|
599 |
}
|
600 |
|
601 |
$pos1 = strpos($content, '# BEGIN WebP Express');
|
@@ -606,11 +152,30 @@ class HTAccess
|
|
606 |
if ($pos2 === false) {
|
607 |
return false;
|
608 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
609 |
|
610 |
-
|
611 |
-
|
612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
613 |
|
|
|
614 |
} else {
|
615 |
// the .htaccess isn't even there. So there are no rules.
|
616 |
return false;
|
@@ -634,6 +199,21 @@ class HTAccess
|
|
634 |
}
|
635 |
}
|
636 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
637 |
public static function saveHTAccessRulesToFile($filename, $rules, $createIfMissing = false) {
|
638 |
if (!@file_exists($filename)) {
|
639 |
if (!$createIfMissing) {
|
@@ -696,6 +276,11 @@ class HTAccess
|
|
696 |
return $success;
|
697 |
}
|
698 |
|
|
|
|
|
|
|
|
|
|
|
699 |
/* only called in this file */
|
700 |
public static function saveHTAccessRulesToFirstWritableHTAccessDir($dirs, $rules)
|
701 |
{
|
@@ -712,35 +297,35 @@ class HTAccess
|
|
712 |
* Try to deactivate all .htaccess rules.
|
713 |
* If success, we return true.
|
714 |
* If we fail, we return an array of filenames that have problems
|
|
|
715 |
*/
|
716 |
-
public static function deactivateHTAccessRules() {
|
717 |
-
//return self::saveHTAccessRules('# Plugin is deactivated');
|
718 |
-
$indexDir = Paths::getIndexDirAbs();
|
719 |
-
$homeDir = Paths::getHomeDirAbs();
|
720 |
-
$wpContentDir = Paths::getContentDirAbs();
|
721 |
-
$pluginDir = Paths::getPluginDirAbs();
|
722 |
-
$uploadDir = Paths::getUploadDirAbs();
|
723 |
-
|
724 |
-
$dirsToClean = [$indexDir, $homeDir, $wpContentDir, $pluginDir, $uploadDir];
|
725 |
|
|
|
|
|
726 |
$failures = [];
|
|
|
727 |
|
728 |
-
foreach ($
|
|
|
729 |
$filename = $dir . '/.htaccess';
|
730 |
if (!FileHelper::fileExists($filename)) {
|
|
|
731 |
continue;
|
732 |
} else {
|
733 |
if (self::haveWeRulesInThisHTAccessBestGuess($filename)) {
|
734 |
-
if (
|
735 |
-
$
|
|
|
|
|
736 |
}
|
|
|
|
|
737 |
}
|
738 |
}
|
739 |
}
|
740 |
-
|
741 |
-
|
742 |
-
}
|
743 |
-
return $failures;
|
744 |
}
|
745 |
|
746 |
public static function testLinks($config) {
|
@@ -789,6 +374,11 @@ class HTAccess
|
|
789 |
$uploadToo = 'depends';
|
790 |
}
|
791 |
|
|
|
|
|
|
|
|
|
|
|
792 |
return [
|
793 |
$minRequired,
|
794 |
$pluginToo, // 'yes', 'no' or 'depends'
|
@@ -796,93 +386,58 @@ class HTAccess
|
|
796 |
];
|
797 |
}
|
798 |
|
799 |
-
/**
|
800 |
-
* Try to save the rules.
|
801 |
-
* Returns many details
|
802 |
-
* (called from migrate1.php, reactivate.php, Config.php and this file)
|
803 |
-
*/
|
804 |
public static function saveRules($config) {
|
|
|
805 |
|
|
|
|
|
|
|
|
|
806 |
|
807 |
-
|
|
|
|
|
|
|
|
|
|
|
808 |
|
809 |
-
$
|
810 |
-
|
811 |
-
|
812 |
|
813 |
-
$
|
814 |
-
if ($wpContentFailed) {
|
815 |
-
if ($minRequired == 'index') {
|
816 |
-
$rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'index');
|
817 |
-
$indexFailed = !(HTAccess::saveHTAccessRulesToFile(Paths::getIndexDirAbs() . '/.htaccess', $rules, true));
|
818 |
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
-
|
823 |
-
|
824 |
-
}
|
825 |
-
}
|
826 |
-
} else {
|
827 |
-
$mainResult = 'wp-content';
|
828 |
-
// TODO: Change to something like "The rules are placed in the .htaccess file in your wp-content dir."
|
829 |
-
// BUT! - current text is searched for in page-messages.php
|
830 |
-
HTAccess::saveHTAccessRulesToFile(Paths::getIndexDirAbs() . '/.htaccess', '# WebP Express has placed its rules in your wp-content dir. Go there.', false);
|
831 |
-
}
|
832 |
|
833 |
-
|
834 |
-
|
835 |
-
|
836 |
-
$
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
841 |
-
}
|
842 |
-
}
|
843 |
-
$pluginFailed = false;
|
844 |
-
$pluginFailedBadly = true;
|
845 |
-
if ($pluginToo == 'yes') {
|
846 |
-
$rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'plugin');
|
847 |
-
$pluginDir = Paths::getPluginDirAbs();
|
848 |
-
$pluginFailed = !(HTAccess::saveHTAccessRulesToFile($pluginDir . '/.htaccess', $rules, true));
|
849 |
-
if ($pluginFailed) {
|
850 |
-
$pluginFailedBadly = self::haveWeRulesInThisHTAccessBestGuess($pluginDir . '/.htaccess');
|
851 |
-
}
|
852 |
-
}
|
853 |
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
858 |
-
} elseif ($mainResult == 'index') {
|
859 |
-
$uploadToo = (Paths::isUploadDirMovedOutOfAbsPath() ? 'yes' : 'no');
|
860 |
} else {
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
867 |
-
$uploadDir = Paths::getUploadDirAbs();
|
868 |
-
$rules = HTAccess::generateHTAccessRulesFromConfigObj($config, 'uploads');
|
869 |
-
$uploadFailed = !(HTAccess::saveHTAccessRulesToFile($uploadDir . '/.htaccess', $rules, true));
|
870 |
-
if ($uploadFailed) {
|
871 |
-
$uploadFailedBadly = self::haveWeRulesInThisHTAccessBestGuess($uploadDir . '/.htaccess');
|
872 |
}
|
873 |
}
|
874 |
|
875 |
-
|
876 |
-
|
877 |
-
'minRequired' => $minRequired, // 'index' or 'wp-content'
|
878 |
-
'overidingRulesInWpContentWarning' => $overidingRulesInWpContentWarning, // true if main result is 'index' but we cannot remove those in wp-content
|
879 |
-
'rules' => $rules, // The rules we generated
|
880 |
-
'pluginToo' => $pluginToo, // 'yes', 'no' or 'depends'
|
881 |
-
'pluginFailed' => $pluginFailed, // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
|
882 |
-
'pluginFailedBadly' => $pluginFailedBadly, // true if plugin failed AND it seems we have rewrite rules there
|
883 |
-
'uploadToo' => $uploadToo, // 'yes', 'no' or 'depends'
|
884 |
-
'uploadFailed' => $uploadFailed,
|
885 |
-
'uploadFailedBadly' => $uploadFailedBadly,
|
886 |
-
];
|
887 |
}
|
|
|
888 |
}
|
4 |
|
5 |
use \WebPExpress\Config;
|
6 |
use \WebPExpress\FileHelper;
|
7 |
+
use \WebPExpress\HTAccessRules;
|
8 |
use \WebPExpress\Paths;
|
9 |
use \WebPExpress\State;
|
10 |
|
11 |
class HTAccess
|
12 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
public static function arePathsUsedInHTAccessOutdated() {
|
14 |
if (!Config::isConfigFileThere()) {
|
15 |
// this properly means that rewrite rules have never been generated
|
17 |
}
|
18 |
|
19 |
$pathsGoingToBeUsedInHtaccess = [
|
|
|
20 |
'wod-url-path' => Paths::getWodUrlPath(),
|
|
|
21 |
];
|
22 |
|
23 |
$config = Config::loadConfig();
|
27 |
}
|
28 |
|
29 |
foreach ($config['paths-used-in-htaccess'] as $prop => $value) {
|
30 |
+
if (isset($pathsGoingToBeUsedInHtaccess[$prop])) {
|
31 |
+
if ($value != $pathsGoingToBeUsedInHtaccess[$prop]) {
|
32 |
+
return true;
|
33 |
+
}
|
34 |
}
|
35 |
}
|
36 |
}
|
58 |
'cache-control-max-age' => 'one-week',
|
59 |
'cache-control-public' => true,
|
60 |
'enable-redirection-to-webp-realizer' => false,
|
61 |
+
'enable-redirection-to-converter' => true,
|
62 |
+
'destination-folder' => 'separate',
|
63 |
+
'destination-extension' => 'append',
|
64 |
+
'destination-structure' => 'doc-root',
|
65 |
+
'scope' => ['themes', 'uploads']
|
66 |
];
|
67 |
|
68 |
+
/*
|
69 |
if (isset($newConfig['redirect-to-existing-in-htaccess']) && $newConfig['redirect-to-existing-in-htaccess']) {
|
70 |
$propsToCompare['destination-folder'] = 'separate';
|
71 |
$propsToCompare['destination-extension'] = 'append';
|
72 |
+
$propsToCompare['destination-structure'] = 'doc-root';
|
73 |
+
}*/
|
74 |
|
75 |
foreach ($propsToCompare as $prop => $behaviourBeforeIntroduced) {
|
76 |
if (!isset($newConfig[$prop])) {
|
135 |
|
136 |
|
137 |
/**
|
138 |
+
* @return string|false Rules, or false if no rules found or file does not exist.
|
|
|
|
|
139 |
*/
|
140 |
+
public static function extractWebPExpressRulesFromHTAccess($filename) {
|
141 |
if (FileHelper::fileExists($filename)) {
|
142 |
$content = FileHelper::loadFile($filename);
|
143 |
if ($content === false) {
|
144 |
+
return false;
|
145 |
}
|
146 |
|
147 |
$pos1 = strpos($content, '# BEGIN WebP Express');
|
152 |
if ($pos2 === false) {
|
153 |
return false;
|
154 |
}
|
155 |
+
return substr($content, $pos1, $pos2 - $pos1);
|
156 |
+
} else {
|
157 |
+
// the .htaccess isn't even there. So there are no rules.
|
158 |
+
return false;
|
159 |
+
}
|
160 |
+
}
|
161 |
|
162 |
+
/**
|
163 |
+
* Sneak peak into .htaccess to see if we have rules in it
|
164 |
+
* This may not be possible (it requires read permission)
|
165 |
+
* Return true, false, or null if we just can't tell
|
166 |
+
*/
|
167 |
+
public static function haveWeRulesInThisHTAccess($filename) {
|
168 |
+
if (FileHelper::fileExists($filename)) {
|
169 |
+
$content = FileHelper::loadFile($filename);
|
170 |
+
if ($content === false) {
|
171 |
+
return null;
|
172 |
+
}
|
173 |
+
$weRules = (self::extractWebPExpressRulesFromHTAccess($filename));
|
174 |
+
if ($weRules === false) {
|
175 |
+
return false;
|
176 |
+
}
|
177 |
|
178 |
+
return (strpos($weRules, '<IfModule ') !== false);
|
179 |
} else {
|
180 |
// the .htaccess isn't even there. So there are no rules.
|
181 |
return false;
|
199 |
}
|
200 |
}
|
201 |
|
202 |
+
public static function getRootsWithWebPExpressRulesIn()
|
203 |
+
{
|
204 |
+
$allIds = Paths::getImageRootIds();
|
205 |
+
$allIds[] = 'cache';
|
206 |
+
$result = [];
|
207 |
+
foreach ($allIds as $imageRootId) {
|
208 |
+
$filename = Paths::getAbsDirById($imageRootId) . '/.htaccess';
|
209 |
+
if (self::haveWeRulesInThisHTAccessBestGuess($filename)) {
|
210 |
+
$result[] = $imageRootId;
|
211 |
+
}
|
212 |
+
|
213 |
+
}
|
214 |
+
return $result;
|
215 |
+
}
|
216 |
+
|
217 |
public static function saveHTAccessRulesToFile($filename, $rules, $createIfMissing = false) {
|
218 |
if (!@file_exists($filename)) {
|
219 |
if (!$createIfMissing) {
|
276 |
return $success;
|
277 |
}
|
278 |
|
279 |
+
public static function saveHTAccessRules($rootId, $rules, $createIfMissing = true) {
|
280 |
+
$filename = Paths::getAbsDirById($rootId) . '/.htaccess';
|
281 |
+
return self::saveHTAccessRulesToFile($filename, $rules, $createIfMissing);
|
282 |
+
}
|
283 |
+
|
284 |
/* only called in this file */
|
285 |
public static function saveHTAccessRulesToFirstWritableHTAccessDir($dirs, $rules)
|
286 |
{
|
297 |
* Try to deactivate all .htaccess rules.
|
298 |
* If success, we return true.
|
299 |
* If we fail, we return an array of filenames that have problems
|
300 |
+
* @return true|array
|
301 |
*/
|
302 |
+
public static function deactivateHTAccessRules($comment = '# Plugin is deactivated') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
+
$rootsToClean = Paths::getImageRootIds();
|
305 |
+
$rootsToClean[] = 'home';
|
306 |
$failures = [];
|
307 |
+
$successes = [];
|
308 |
|
309 |
+
foreach ($rootsToClean as $imageRootId) {
|
310 |
+
$dir = Paths::getAbsDirById($imageRootId);
|
311 |
$filename = $dir . '/.htaccess';
|
312 |
if (!FileHelper::fileExists($filename)) {
|
313 |
+
//error_log('exists not:' . $filename);
|
314 |
continue;
|
315 |
} else {
|
316 |
if (self::haveWeRulesInThisHTAccessBestGuess($filename)) {
|
317 |
+
if (self::saveHTAccessRulesToFile($filename, $comment, false)) {
|
318 |
+
$successes[] = $imageRootId;
|
319 |
+
} else {
|
320 |
+
$failures[] = $imageRootId;
|
321 |
}
|
322 |
+
} else {
|
323 |
+
//error_log('no rules:' . $filename);
|
324 |
}
|
325 |
}
|
326 |
}
|
327 |
+
$success = (count($failures) == 0);
|
328 |
+
return [$success, $failures, $successes];
|
|
|
|
|
329 |
}
|
330 |
|
331 |
public static function testLinks($config) {
|
374 |
$uploadToo = 'depends';
|
375 |
}
|
376 |
|
377 |
+
// We need upload too for rewrite rules when destination structure is image-roots.
|
378 |
+
// but it is also good otherwise. So lets always do it.
|
379 |
+
|
380 |
+
$uploadToo = 'yes';
|
381 |
+
|
382 |
return [
|
383 |
$minRequired,
|
384 |
$pluginToo, // 'yes', 'no' or 'depends'
|
386 |
];
|
387 |
}
|
388 |
|
|
|
|
|
|
|
|
|
|
|
389 |
public static function saveRules($config) {
|
390 |
+
list($success, $failedDeactivations, $successfulDeactivations) = self::deactivateHTAccessRules('# The rules have left the building');
|
391 |
|
392 |
+
$rootsToPutRewritesIn = $config['scope'];
|
393 |
+
if ($config['destination-structure'] == 'doc-root') {
|
394 |
+
$rootsToPutRewritesIn = Paths::filterOutSubRoots($rootsToPutRewritesIn);
|
395 |
+
}
|
396 |
|
397 |
+
$dirsContainingWebps = [];
|
398 |
+
$mingled = ($config['destination-folder'] == 'mingled');
|
399 |
+
if ($mingled) {
|
400 |
+
$dirsContainingWebps[] = 'uploads';
|
401 |
+
}
|
402 |
+
$scopeOtherThanUpload = (str_replace('uploads', '', implode(',', $config['scope'])) != '');
|
403 |
|
404 |
+
if ($scopeOtherThanUpload || (!$mingled)) {
|
405 |
+
$dirsContainingWebps[] = 'cache';
|
406 |
+
}
|
407 |
|
408 |
+
$dirsToPutRewritesIn = array_merge($rootsToPutRewritesIn, $dirsContainingWebps);
|
|
|
|
|
|
|
|
|
409 |
|
410 |
+
$failedWrites = [];
|
411 |
+
$successfullWrites = [];
|
412 |
+
foreach ($dirsToPutRewritesIn as $rootId) {
|
413 |
+
$dirContainsSourceImages = in_array($rootId, $rootsToPutRewritesIn);
|
414 |
+
$dirContainsWebPImages = in_array($rootId, $dirsContainingWebps);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
|
416 |
+
$rules = HTAccessRules::generateHTAccessRulesFromConfigObj($config, $rootId, $dirContainsSourceImages, $dirContainsWebPImages);
|
417 |
+
$success = self::saveHTAccessRules(
|
418 |
+
$rootId,
|
419 |
+
$rules,
|
420 |
+
true
|
421 |
+
);
|
422 |
+
if ($success) {
|
423 |
+
$successfullWrites[] = $rootId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
424 |
|
425 |
+
// Remove it from $successfulDeactivations (if it is there)
|
426 |
+
if (($key = array_search($rootId, $successfulDeactivations)) !== false) {
|
427 |
+
unset($successfulDeactivations[$key]);
|
428 |
+
}
|
|
|
|
|
429 |
} else {
|
430 |
+
$failedWrites[] = $rootId;
|
431 |
+
|
432 |
+
// Remove it from $failedDeactivations (if it is there)
|
433 |
+
if (($key = array_search($rootId, $failedDeactivations)) !== false) {
|
434 |
+
unset($failedDeactivations[$key]);
|
435 |
+
}
|
|
|
|
|
|
|
|
|
|
|
436 |
}
|
437 |
}
|
438 |
|
439 |
+
$success = ((count($failedDeactivations) == 0) && (count($failedWrites) == 0));
|
440 |
+
return [$success, $successfullWrites, $successfulDeactivations, $failedWrites, $failedDeactivations];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 |
}
|
442 |
+
|
443 |
}
|
lib/classes/HTAccessRules.php
ADDED
@@ -0,0 +1,855 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
class HTAccessRules
|
6 |
+
{
|
7 |
+
private static $useDocRootForStructuringCacheDir;
|
8 |
+
private static $config;
|
9 |
+
private static $mingled;
|
10 |
+
private static $appendWebP;
|
11 |
+
private static $imageTypes;
|
12 |
+
private static $fileExt;
|
13 |
+
private static $fileExtensions;
|
14 |
+
private static $fileExtIncludingDot;
|
15 |
+
private static $htaccessDir;
|
16 |
+
private static $htaccessDirRelToDocRoot;
|
17 |
+
private static $htaccessDirAbs;
|
18 |
+
private static $modHeaderDefinitelyUnavailable;
|
19 |
+
private static $passThroughHeaderDefinitelyUnavailable;
|
20 |
+
private static $passThroughHeaderDefinitelyAvailable;
|
21 |
+
private static $passThroughEnvVarDefinitelyUnavailable;
|
22 |
+
private static $passThroughEnvVarDefinitelyAvailable;
|
23 |
+
private static $capTests;
|
24 |
+
private static $addVary;
|
25 |
+
private static $dirContainsSourceImages;
|
26 |
+
private static $dirContainsWebPImages;
|
27 |
+
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @return string Info in comments.
|
31 |
+
*/
|
32 |
+
private static function infoRules()
|
33 |
+
{
|
34 |
+
|
35 |
+
return "# The rules below is a result of many parameters, including the following:\n" .
|
36 |
+
"#\n# WebP Express options:\n" .
|
37 |
+
"# - Redirection to existing webp: " .
|
38 |
+
(self::$config['redirect-to-existing-in-htaccess'] ? 'enabled' : 'disabled') . "\n" .
|
39 |
+
"# - Redirection to converter: " .
|
40 |
+
(self::$config['enable-redirection-to-converter'] ? 'enabled' : 'disabled') . "\n" .
|
41 |
+
"# - Redirection to converter to create missing webp files upon request for the webp: " .
|
42 |
+
(self::$config['enable-redirection-to-webp-realizer'] ? 'enabled' : 'disabled') . "\n" .
|
43 |
+
|
44 |
+
"# - Destination folder: " . self::$config['destination-folder'] . "\n" .
|
45 |
+
"# - Destination extension: " . self::$config['destination-extension'] . "\n" .
|
46 |
+
"# - Destination structure: " . self::$config['destination-structure'] . (((self::$config['destination-structure'] == 'doc-root') && (!self::$useDocRootForStructuringCacheDir)) ? ' (overruled!)' : '') . "\n" .
|
47 |
+
"# - Image types: " . str_replace('?', '', implode(', ', self::$fileExtensions)) . "\n" .
|
48 |
+
|
49 |
+
"#\n# Wordpress/Server configuration:\n" .
|
50 |
+
'# - Document root availablity: ' . Paths::docRootStatusText() . "\n" .
|
51 |
+
|
52 |
+
"#\n# .htaccess capability test results:\n" .
|
53 |
+
"# - mod_header working?: " .
|
54 |
+
(self::$capTests['modHeaderWorking'] === true ? 'yes' : (self::$capTests['modHeaderWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
|
55 |
+
"# - pass variable from .htaccess to script through header working?: " .
|
56 |
+
(self::$capTests['passThroughHeaderWorking'] === true ? 'yes' : (self::$capTests['passThroughHeaderWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
|
57 |
+
"# - pass variable from .htaccess to script through environment variable working?: " .
|
58 |
+
(self::$capTests['passThroughEnvWorking'] === true ? 'yes' : (self::$capTests['passThroughEnvWorking'] === false ? 'no' : 'could not be determined')) . "\n" .
|
59 |
+
|
60 |
+
"#\n# Role of the dir that this .htaccess is located in:\n" .
|
61 |
+
'# - Is this .htaccess in a dir containing source images?: ' . (self::$dirContainsSourceImages ? 'yes' : 'no') . "\n" .
|
62 |
+
'# - Is this .htaccess in a dir containing webp images?: ' . (self::$dirContainsWebPImages ? 'yes' : 'no') . "\n" .
|
63 |
+
"\n";
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* @return string rules for cache control
|
68 |
+
*/
|
69 |
+
private static function cacheRules()
|
70 |
+
{
|
71 |
+
// Build cache control rules
|
72 |
+
$ccRules = '';
|
73 |
+
$cacheControlHeader = Config::getCacheControlHeader(self::$config);
|
74 |
+
if ($cacheControlHeader != '') {
|
75 |
+
$ccRules .= "# Set Cache-Control header for requests to webp images\n";
|
76 |
+
$ccRules .= "<IfModule mod_headers.c>\n";
|
77 |
+
$ccRules .= " <FilesMatch \"\.webp$\">\n";
|
78 |
+
$ccRules .= " Header set Cache-Control \"" . $cacheControlHeader . "\"\n";
|
79 |
+
$ccRules .= " </FilesMatch>\n";
|
80 |
+
$ccRules .= "</IfModule>\n\n";
|
81 |
+
|
82 |
+
// Fall back to mod_expires if mod_headers is unavailable
|
83 |
+
if (self::$modHeaderDefinitelyUnavailable) {
|
84 |
+
$cacheControl = self::$config['cache-control'];
|
85 |
+
|
86 |
+
$expires = '';
|
87 |
+
if ($cacheControl == 'custom') {
|
88 |
+
$expires = '';
|
89 |
+
|
90 |
+
// Do not add Expire header if private is set
|
91 |
+
// - because then the user don't want caching in proxies / CDNs.
|
92 |
+
// the Expires header doesn't differentiate between private/public
|
93 |
+
if (!(preg_match('/private/', self::$config['cache-control-custom']))) {
|
94 |
+
if (preg_match('/max-age=(\d+)/', self::$config['cache-control-custom'], $matches)) {
|
95 |
+
if (isset($matches[1])) {
|
96 |
+
$expires = $matches[1] . ' seconds';
|
97 |
+
}
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
} elseif ($cacheControl == 'no-header') {
|
102 |
+
$expires = '';
|
103 |
+
} elseif ($cacheControl == 'set') {
|
104 |
+
if (self::$config['cache-control-public']) {
|
105 |
+
$cacheControlOptions = [
|
106 |
+
'no-header' => '',
|
107 |
+
'one-second' => '1 seconds',
|
108 |
+
'one-minute' => '1 minutes',
|
109 |
+
'one-hour' => '1 hours',
|
110 |
+
'one-day' => '1 days',
|
111 |
+
'one-week' => '1 weeks',
|
112 |
+
'one-month' => '1 months',
|
113 |
+
'one-year' => '1 years',
|
114 |
+
];
|
115 |
+
$expires = $cacheControlOptions[self::$config['cache-control-max-age']];
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
+
if ($expires != '') {
|
120 |
+
// in case mod_headers is missing, try mod_expires
|
121 |
+
$ccRules .= "# Fall back to mod_expires if mod_headers is unavailable\n";
|
122 |
+
$ccRules .= "<IfModule !mod_headers.c>\n";
|
123 |
+
$ccRules .= " <IfModule mod_expires.c>\n";
|
124 |
+
$ccRules .= " ExpiresActive On\n";
|
125 |
+
$ccRules .= " ExpiresByType image/webp \"access plus " . $expires . "\"\n";
|
126 |
+
$ccRules .= " </IfModule>\n";
|
127 |
+
$ccRules .= "</IfModule>\n\n";
|
128 |
+
}
|
129 |
+
}
|
130 |
+
}
|
131 |
+
return $ccRules;
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* @return string rules for redirecting to existing
|
136 |
+
*/
|
137 |
+
private static function redirectToExistingRules()
|
138 |
+
{
|
139 |
+
$rules = '';
|
140 |
+
|
141 |
+
|
142 |
+
if (self::$mingled) {
|
143 |
+
// TODO:
|
144 |
+
// Only write mingled rules for "uploads" dir.
|
145 |
+
// - UNLESS no .htaccess has been placed in uploads dir (is unwritable) (in that case also write for wp-content / index)
|
146 |
+
// (self::$htaccessDir == 'uploads')
|
147 |
+
$rules .= " # Redirect to existing converted image in same dir (if browser supports webp)\n";
|
148 |
+
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
|
149 |
+
|
150 |
+
if (self::$htaccessDir == 'index') {
|
151 |
+
// TODO: Add the following rule if configured to
|
152 |
+
if (false) {
|
153 |
+
// TODO: Full path to wp-admin from doc-root - if possible
|
154 |
+
// (that is: if document root is available).
|
155 |
+
// ie: RewriteCond %{REQUEST_URI} ^/?wordpress/wp-admin
|
156 |
+
$rules .= " RewriteCond %{REQUEST_URI} !wp-admin\n";
|
157 |
+
}
|
158 |
+
}
|
159 |
+
|
160 |
+
// $rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
161 |
+
|
162 |
+
// self::$appendWebP cannot be used, we need this:
|
163 |
+
// (because we are not sure there are a .htaccess in the uploads folder)
|
164 |
+
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
165 |
+
|
166 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
167 |
+
$rules .= " RewriteCond %1" . ($appendWebP ? "%2" : "") . "\.webp -f\n";
|
168 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ %1" . ($appendWebP ? "%2" : "") .
|
169 |
+
"\.webp [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
170 |
+
|
171 |
+
/*
|
172 |
+
if (self::$config['destination-extension'] == 'append') {
|
173 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME}.webp -f\n";
|
174 |
+
//$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp -f\n";
|
175 |
+
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ $1.$2.webp [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
176 |
+
} else {
|
177 |
+
// extension: set to webp
|
178 |
+
|
179 |
+
//$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . self::$htaccessDirRelToDocRoot . "/$1.webp -f\n";
|
180 |
+
//$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ $1.webp [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
181 |
+
|
182 |
+
// Got these new rules here: https://www.digitalocean.com/community/tutorials/how-to-create-and-serve-webp-images-to-speed-up-your-website
|
183 |
+
$rules .= " RewriteCond %{REQUEST_URI} (?i)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
184 |
+
$rules .= " RewriteCond %{DOCUMENT_ROOT}%1\.webp -f\n";
|
185 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ %1\.webp [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
186 |
+
|
187 |
+
// Instead of using REQUEST_URI, I can use REQUEST_FILENAME and remove DOCUMENT_ROOT
|
188 |
+
// I suppose REQUEST_URI is what was requested (ie "/wp-content/uploads/image.jpg").
|
189 |
+
// REQUEST_FILENAME is the filesystem path. (ie "/var/www/example.com/uploads-moved/image.jpg")
|
190 |
+
// But it cant be, because then the digitalocean solution would not work in above case.
|
191 |
+
// TODO: investigate
|
192 |
+
|
193 |
+
// RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1\.webp [T=image/webp,E=EXISTING:1,E=ADDVARY:1,L]
|
194 |
+
}
|
195 |
+
*/
|
196 |
+
}
|
197 |
+
|
198 |
+
if (self::$htaccessDir != 'uploads') {
|
199 |
+
//return '# temporalily disabled';
|
200 |
+
}
|
201 |
+
|
202 |
+
// Redirect to existing converted image in cache-dir.
|
203 |
+
// Do not write these rules for uploads in mingled (there are no "uploads" images in cache-dir when in mingled mode)
|
204 |
+
if (!(self::$mingled && (self::$htaccessDir == 'uploads'))) {
|
205 |
+
$rules .= " # Redirect to existing converted image in cache-dir (if browser supports webp)\n";
|
206 |
+
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
|
207 |
+
|
208 |
+
if (self::$useDocRootForStructuringCacheDir) {
|
209 |
+
$cacheDirRel = Paths::getCacheDirRelToDocRoot() . '/doc-root';
|
210 |
+
|
211 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
212 |
+
$rules .= " RewriteCond %{DOCUMENT_ROOT}/" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp -f\n";
|
213 |
+
$rules .= " RewriteRule ^/?(.+)\.(" . self::$fileExt . ")$ /" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot .
|
214 |
+
"/$1.$2.webp [NC,T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
215 |
+
|
216 |
+
} else {
|
217 |
+
// Make sure source image exists
|
218 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
219 |
+
|
220 |
+
// Find relative path of source (accessible as %2%3)
|
221 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(" . self::$htaccessDirAbs . "/)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
222 |
+
|
223 |
+
// Make sure there is a webp in the cache-dir
|
224 |
+
$cacheDirForThisRoot = Paths::getCacheDirForImageRoot(
|
225 |
+
self::$config['destination-folder'],
|
226 |
+
self::$config['destination-structure'],
|
227 |
+
self::$htaccessDir
|
228 |
+
);
|
229 |
+
$cacheDirForThisRoot = PathHelper::fixAbsPathToUseUnresolvedDocRoot($cacheDirForThisRoot);
|
230 |
+
|
231 |
+
$rules .= " RewriteCond " . $cacheDirForThisRoot . "/%2%3.webp -f\n";
|
232 |
+
//RewriteCond /var/www/webp-express-tests/we0/wp-content-moved/webp-express/webp-images/uploads/%2%3.webp -f
|
233 |
+
|
234 |
+
$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
235 |
+
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
236 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath .
|
237 |
+
" [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
238 |
+
}
|
239 |
+
|
240 |
+
//$rules .= " RewriteRule ^\/?(.*)\.(" . self::$fileExt . ")$ /" . $cacheDirRel . "/" . self::$htaccessDirRelToDocRoot . "/$1.$2.webp [NC,T=image/webp,E=EXISTING:1,L]\n\n";
|
241 |
+
}
|
242 |
+
|
243 |
+
return $rules;
|
244 |
+
}
|
245 |
+
|
246 |
+
private static function webpRealizerRules()
|
247 |
+
{
|
248 |
+
/*
|
249 |
+
# Pass REQUEST_FILENAME to PHP in request header
|
250 |
+
RewriteCond %{REQUEST_FILENAME} !-f
|
251 |
+
RewriteCond %{DOCUMENT_ROOT}/wordpress/uploads-moved/$1 -f
|
252 |
+
RewriteRule ^(.*)\.(webp)$ - [E=REQFN:%{REQUEST_FILENAME}]
|
253 |
+
<IfModule mod_headers.c>
|
254 |
+
RequestHeader set REQFN "%{REQFN}e" env=REQFN
|
255 |
+
</IfModule>
|
256 |
+
|
257 |
+
# WebP Realizer: Redirect non-existing webp images to converter when a corresponding jpeg/png is found
|
258 |
+
RewriteCond %{REQUEST_FILENAME} !-f
|
259 |
+
RewriteCond %{DOCUMENT_ROOT}/wordpress/uploads-moved/$1 -f
|
260 |
+
RewriteRule ^(.*)\.(webp)$ /plugins-moved/webp-express/wod/webp-realizer.php?wp-content=wp-content-moved [NC,L]
|
261 |
+
*/
|
262 |
+
|
263 |
+
$rules = '';
|
264 |
+
$rules .= "# WebP Realizer: Redirect non-existing webp images to webp-realizer.php, which will locate corresponding jpg/png, \n" .
|
265 |
+
"# convert it, and deliver the freshly converted webp\n";
|
266 |
+
$rules .= "<IfModule mod_rewrite.c>\n" .
|
267 |
+
" RewriteEngine On\n";
|
268 |
+
|
269 |
+
|
270 |
+
if (self::$useDocRootForStructuringCacheDir) {
|
271 |
+
/*
|
272 |
+
Generate something like this:
|
273 |
+
|
274 |
+
RewriteCond %{REQUEST_FILENAME} !-f
|
275 |
+
RewriteRule ^/?(.+)\.(webp)$ /plugins-moved/webp-express/wod/webp-realizer.php [E=DESTINATIONREL:wp-content-moved/$0,E=WPCONTENT:wp-content-moved,NC,L]
|
276 |
+
*/
|
277 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
|
278 |
+
$params = [];
|
279 |
+
$flags = [];
|
280 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
281 |
+
$flags[] = 'E=DESTINATIONREL:' . self::$htaccessDirRelToDocRoot . '/$0';
|
282 |
+
}
|
283 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
284 |
+
$flags[] = 'E=WPCONTENT:' . Paths::getContentDirRel();
|
285 |
+
}
|
286 |
+
$flags[] = 'NC';
|
287 |
+
$flags[] = 'L';
|
288 |
+
|
289 |
+
$passRelativePathToSourceInQS = !(self::$passThroughEnvVarDefinitelyAvailable || self::$passThroughHeaderDefinitelyAvailable);
|
290 |
+
if ($passRelativePathToSourceInQS) {
|
291 |
+
$params[] = 'xdestination-rel=x' . self::$htaccessDirRelToDocRoot . '/$1.$2';
|
292 |
+
}
|
293 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
294 |
+
$params[] = "wp-content=" . Paths::getContentDirRel();
|
295 |
+
}
|
296 |
+
|
297 |
+
$rewriteRuleStart = '^/?(.+)';
|
298 |
+
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(webp)$ " .
|
299 |
+
"/" . Paths::getWebPRealizerUrlPath() .
|
300 |
+
((count($params) > 0) ? "?" . implode('&', $params) : '') .
|
301 |
+
" [" . implode(',', $flags) . "]\n\n";
|
302 |
+
} else {
|
303 |
+
/*
|
304 |
+
Generate something like this:
|
305 |
+
RewriteCond %{REQUEST_FILENAME} !-f
|
306 |
+
RewriteRule (?i).*(\.jpe?g|\.png)\.webp$ /plugins-moved/webp-express/wod/webp-realizer.php [E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:../wp-content-moved,E=WE_DESTINATION_REL_HTACCESS:$0,E=WE_HTACCESS_ID:cache,NC,L]
|
307 |
+
*/
|
308 |
+
// Add condition for making sure the webp does not already exist
|
309 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
|
310 |
+
|
311 |
+
$params = [];
|
312 |
+
$flags = [];
|
313 |
+
|
314 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
315 |
+
$flags[] = 'E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:' . Paths::getContentDirRelToPluginDir();
|
316 |
+
$flags[] = 'E=WE_DESTINATION_REL_HTACCESS:$0';
|
317 |
+
$flags[] = 'E=WE_HTACCESS_ID:' . self::$htaccessDir; // this will btw either be "uploads" or "cache"
|
318 |
+
}
|
319 |
+
$flags[] = 'NC'; // case-insensitive match (so file extension can be jpg, JPG or even jPg)
|
320 |
+
$flags[] = 'L';
|
321 |
+
|
322 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
323 |
+
$params[] = 'xwp-content-rel-to-plugin-dir=x' . Paths::getContentDirRelToPluginDir();
|
324 |
+
$params[] = 'xdestination-rel-htaccess=x$0';
|
325 |
+
$params[] = 'htaccess-id=' . self::$htaccessDir;
|
326 |
+
}
|
327 |
+
|
328 |
+
// self::$appendWebP cannot be used, we need the following in order for
|
329 |
+
// it to work for uploads in: Mingled, "Set to WebP", "Image roots".
|
330 |
+
// TODO! Will it work for ie theme images?
|
331 |
+
// - well, it should, because the script is passed $0. Not matching the ".png" part of the filename
|
332 |
+
// only means it is a bit more greedy than it has to
|
333 |
+
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
334 |
+
|
335 |
+
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "\.webp$ " .
|
336 |
+
"/" . Paths::getWebPRealizerUrlPath() .
|
337 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
338 |
+
" [" . implode(',', $flags) . "]\n";
|
339 |
+
|
340 |
+
/*
|
341 |
+
Generate something like this:
|
342 |
+
RewriteCond %{REQUEST_FILENAME} !-f
|
343 |
+
RewriteRule (?i).*\.webp$ /plugins-moved/webp-express/wod/webp-realizer.php [E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:../wp-content-moved,E=WE_DESTINATION_REL_IMAGE_ROOT:$0,E=WE_IMAGE_ROOT_ID:wp-content,NC,L]
|
344 |
+
*/
|
345 |
+
/*
|
346 |
+
// Add condition for making sure the webp does not already exist
|
347 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
|
348 |
+
|
349 |
+
$params = [];
|
350 |
+
$flags = [];
|
351 |
+
|
352 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
353 |
+
$flags[] = 'E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:' . Paths::getContentDirRelToPluginDir();
|
354 |
+
$flags[] = 'E=WE_DESTINATION_REL_IMAGE_ROOT:$0';
|
355 |
+
$flags[] = 'E=WE_IMAGE_ROOT_ID:' . self::$htaccessDir;
|
356 |
+
}
|
357 |
+
$flags[] = 'NC'; // case-insensitive match (so file extension can be jpg, JPG or even jPg)
|
358 |
+
$flags[] = 'L';
|
359 |
+
|
360 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
361 |
+
$params[] = 'image-root-id=' . self::$htaccessDir;
|
362 |
+
$params[] = 'xdestination-rel-image-root=x$0';
|
363 |
+
$params[] = 'xwp-content-rel-to-plugin-dir=x' . Paths::getContentDirRelToPluginDir();
|
364 |
+
}
|
365 |
+
|
366 |
+
// self::$appendWebP cannot be used, we need the following in order for
|
367 |
+
// it to work for uploads in: Mingled, "Set to WebP", "Image roots".
|
368 |
+
// TODO! Will it work for ie theme images?
|
369 |
+
// - well, it should, because the script is passed $0. Not matching the ".png" part of the filename
|
370 |
+
// only means it is a bit more greedy than it has to
|
371 |
+
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
372 |
+
|
373 |
+
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "\.webp$ " .
|
374 |
+
"/" . Paths::getWebPRealizerUrlPath() .
|
375 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
376 |
+
" [" . implode(',', $flags) . "]\n\n";
|
377 |
+
*/
|
378 |
+
|
379 |
+
|
380 |
+
/*
|
381 |
+
Generate something like this:
|
382 |
+
|
383 |
+
RewriteCond %{REQUEST_FILENAME} !-f
|
384 |
+
RewriteCond %{REQUEST_FILENAME} (?i)(/var/www/webp-express-tests/we0/wordpress/uploads-moved/)(.*)(\.jpe?g|\.png)(\.webp)$
|
385 |
+
RewriteRule (?i).*\.webp$ /plugins-moved/webp-express/wod/webp-realizer.php?root-id=uploads&xdest-rel-to-root-id=x%2%3%4 [E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:../wp-content-moved,E=REQFN:%{REQUEST_FILENAME},NC,L]
|
386 |
+
|
387 |
+
*/
|
388 |
+
/*
|
389 |
+
// Bugger! When the requested file does not exist, %{REQUEST_FILENAME} will always contain the full path.
|
390 |
+
// - it is set to the closest existing path plus one path component.
|
391 |
+
// So we cannot use %{REQUEST_FILENAME} for webp realizer.
|
392 |
+
// It seems we must use REQUEST_URI. But this could get tricky as we may not have access to the resolved document root in the scripts
|
393 |
+
|
394 |
+
// Add condition for making sure the webp does not already exist
|
395 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} !-f\n";
|
396 |
+
|
397 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(" .
|
398 |
+
self::$htaccessDirAbs . "/)(.*)" . (self::$appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "(\.webp)$\n";
|
399 |
+
|
400 |
+
$params = [];
|
401 |
+
$flags = [];
|
402 |
+
|
403 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
404 |
+
$flags[] = 'E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:' . Paths::getContentDirRelToPluginDir();
|
405 |
+
$flags[] = 'E=WEDESTINATIONABS:%0';
|
406 |
+
}
|
407 |
+
$flags[] = 'NC'; // case-insensitive match (so file extension can be jpg, JPG or even jPg)
|
408 |
+
$flags[] = 'L';
|
409 |
+
|
410 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
411 |
+
$params[] = 'root-id=' . self::$htaccessDir;
|
412 |
+
$params[] = 'xdest-rel-to-root-id=x%2%3' . (self::$appendWebP ? "%4" : "");
|
413 |
+
}
|
414 |
+
|
415 |
+
$rules .= " RewriteRule (?i).*\.webp$ " .
|
416 |
+
"/" . Paths::getWebPRealizerUrlPath() .
|
417 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
418 |
+
" [" . implode(',', $flags) . "]\n\n";
|
419 |
+
*/
|
420 |
+
}
|
421 |
+
|
422 |
+
/*if (!self::$config['redirect-to-existing-in-htaccess']) {
|
423 |
+
$rules .= self::cacheRules();
|
424 |
+
}*/
|
425 |
+
$rules .= "</IfModule>\n\n";
|
426 |
+
|
427 |
+
return $rules;
|
428 |
+
}
|
429 |
+
|
430 |
+
private static function webpOnDemandRules()
|
431 |
+
{
|
432 |
+
$setEnvVar = !self::$passThroughEnvVarDefinitelyUnavailable;
|
433 |
+
$passRelativePathToSourceInQS = !(self::$passThroughEnvVarDefinitelyAvailable || self::$passThroughHeaderDefinitelyAvailable);
|
434 |
+
|
435 |
+
$rules = '';
|
436 |
+
|
437 |
+
// Do not add header magic if passing through env is definitely working
|
438 |
+
// Do not add either, if we definitily know it isn't working
|
439 |
+
/*
|
440 |
+
if ((!self::$passThroughEnvVarDefinitelyAvailable) && (!self::$passThroughHeaderDefinitelyUnavailable)) {
|
441 |
+
if (self::$config['enable-redirection-to-converter']) {
|
442 |
+
$rules .= " # Pass REQUEST_FILENAME to webp-on-demand.php in request header\n";
|
443 |
+
//$rules .= $basicConditions;
|
444 |
+
//$rules .= " RewriteRule ^(.*)\.(" . self::$fileExt . ")$ - [E=REQFN:%{REQUEST_FILENAME}]\n" .
|
445 |
+
$rules .= " <IfModule mod_headers.c>\n" .
|
446 |
+
" RequestHeader set REQFN \"%{REQFN}e\" env=REQFN\n" .
|
447 |
+
" </IfModule>\n\n";
|
448 |
+
|
449 |
+
}
|
450 |
+
if (self::$config['enable-redirection-to-webp-realizer']) {
|
451 |
+
// We haven't implemented a clever way to pass through header for webp-realizer yet
|
452 |
+
}
|
453 |
+
}*/
|
454 |
+
$rules .= " # Redirect images to webp-on-demand.php ";
|
455 |
+
if (self::$config['only-redirect-to-converter-for-webp-enabled-browsers']) {
|
456 |
+
$rules .= "(if browser supports webp)\n";
|
457 |
+
} else {
|
458 |
+
$rules .= "(regardless whether browser supports webp or not!)\n";
|
459 |
+
}
|
460 |
+
if (self::$config['only-redirect-to-converter-for-webp-enabled-browsers']) {
|
461 |
+
$rules .= " RewriteCond %{HTTP_ACCEPT} image/webp\n";
|
462 |
+
}
|
463 |
+
|
464 |
+
if (self::$useDocRootForStructuringCacheDir) {
|
465 |
+
/*
|
466 |
+
Generate something like this:
|
467 |
+
|
468 |
+
RewriteCond %{HTTP_ACCEPT} image/webp
|
469 |
+
RewriteCond %{REQUEST_FILENAME} -f
|
470 |
+
RewriteRule ^/?(.+)\.(jpe?g|png)$ /plugins-moved/webp-express/wod/webp-on-demand.php [NC,L,E=REQFN:%{REQUEST_FILENAME},E=WPCONTENT:wp-content-moved]
|
471 |
+
*/
|
472 |
+
|
473 |
+
$params = [];
|
474 |
+
$flags = ['NC', 'L'];
|
475 |
+
if ($setEnvVar) {
|
476 |
+
$flags[] = 'E=REQFN:%{REQUEST_FILENAME}';
|
477 |
+
}
|
478 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
479 |
+
if ($passRelativePathToSourceInQS) {
|
480 |
+
$params[] = 'xsource-rel=x' . self::$htaccessDirRelToDocRoot . '/$1.$2';
|
481 |
+
}
|
482 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
483 |
+
$params[] = "wp-content=" . Paths::getContentDirRel();
|
484 |
+
}
|
485 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
486 |
+
$flags[] = 'E=WPCONTENT:' . Paths::getContentDirRel();
|
487 |
+
}
|
488 |
+
|
489 |
+
// TODO: When $rewriteRuleStart is empty, we don't need the .*, do we? - test
|
490 |
+
$rewriteRuleStart = '^/?(.+)';
|
491 |
+
$rules .= " RewriteRule " . $rewriteRuleStart . "\.(" . self::$fileExt . ")$ " .
|
492 |
+
"/" . Paths::getWodUrlPath() .
|
493 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
494 |
+
" [" . implode(',', $flags) . "]\n";
|
495 |
+
|
496 |
+
} else {
|
497 |
+
|
498 |
+
/*
|
499 |
+
Create something like this:
|
500 |
+
|
501 |
+
RewriteCond %{REQUEST_FILENAME} -f
|
502 |
+
RewriteRule (?i).*(\.jpe?g|\.png)$ /plugins-moved/webp-express/wod/webp-on-demand.php [E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:../../we0-content,E=WE_SOURCE_REL_HTACCESS:$0,E=WE_HTACCESS_ID:themes,NC,L]
|
503 |
+
*/
|
504 |
+
|
505 |
+
// Making sure the source exists
|
506 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
507 |
+
|
508 |
+
$params = [];
|
509 |
+
$flags = [];
|
510 |
+
|
511 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
512 |
+
$flags[] = 'E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:' . Paths::getContentDirRelToPluginDir();
|
513 |
+
$flags[] = 'E=WE_SOURCE_REL_HTACCESS:$0';
|
514 |
+
$flags[] = 'E=WE_HTACCESS_ID:' . self::$htaccessDir; // this will btw be one of the image roots. It will not be "cache"
|
515 |
+
}
|
516 |
+
$flags[] = 'NC'; // case-insensitive match (so file extension can be jpg, JPG or even jPg)
|
517 |
+
$flags[] = 'L';
|
518 |
+
|
519 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
520 |
+
$params[] = 'xwp-content-rel-to-plugin-dir=x' . Paths::getContentDirRelToPluginDir();
|
521 |
+
$params[] = 'xsource-rel-htaccess=x$0';
|
522 |
+
$params[] = 'htaccess-id=' . self::$htaccessDir;
|
523 |
+
}
|
524 |
+
|
525 |
+
// self::$appendWebP cannot be used, we need the following in order for
|
526 |
+
// it to work for uploads in: Mingled, "Set to WebP", "Image roots".
|
527 |
+
// TODO! Will it work for ie theme images?
|
528 |
+
// - well, it should, because the script is passed $0. Not matching the ".png" part of the filename
|
529 |
+
// only means it is a bit more greedy than it has to
|
530 |
+
$appendWebP = !(self::$config['destination-extension'] == 'set');
|
531 |
+
|
532 |
+
$rules .= " RewriteRule (?i).*" . ($appendWebP ? "(" . self::$fileExtIncludingDot . ")" : "") . "$ " .
|
533 |
+
"/" . Paths::getWodUrlPath() .
|
534 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
535 |
+
" [" . implode(',', $flags) . "]\n";
|
536 |
+
|
537 |
+
|
538 |
+
/*
|
539 |
+
*/
|
540 |
+
|
541 |
+
/*
|
542 |
+
Create something like this (for wp-content):
|
543 |
+
|
544 |
+
# Redirect to existing converted image in cache-dir (if browser supports webp)
|
545 |
+
RewriteCond %{HTTP_ACCEPT} image/webp
|
546 |
+
RewriteCond %{REQUEST_FILENAME} (?i)(/var/www/webp-express-tests/we0/wp-content-moved/)(.*)(\.jpe?g|\.png)$
|
547 |
+
RewriteRule (?i)(.*)(\.jpe?g|\.png)$ /plugins-moved/webp-express/wod/webp-on-demand.php?root-id=wp-content&xsource-rel-to-root-id=%2%3
|
548 |
+
|
549 |
+
PS: Actually, the whole REQUEST_FILENAME could be passed in querystring by adding "&req-fn=%0" to above.
|
550 |
+
*/
|
551 |
+
/*
|
552 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(" .
|
553 |
+
self::$htaccessDirAbs . "/)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
554 |
+
|
555 |
+
$params = [];
|
556 |
+
$flags = [];
|
557 |
+
|
558 |
+
if (!self::$passThroughEnvVarDefinitelyUnavailable) {
|
559 |
+
$flags[] = 'E=WE_WP_CONTENT_REL_TO_PLUGIN_DIR:' . Paths::getContentDirRelToPluginDir();
|
560 |
+
$flags[] = 'E=REQFN:%{REQUEST_FILENAME}';
|
561 |
+
}
|
562 |
+
$flags[] = 'NC'; // case-insensitive match (so file extension can be jpg, JPG or even jPg)
|
563 |
+
$flags[] = 'L';
|
564 |
+
|
565 |
+
$params[] = 'root-id=' . self::$htaccessDir;
|
566 |
+
$params[] = 'xsource-rel-to-root-id=x%2' . (self::$appendWebP ? "%3" : "");
|
567 |
+
|
568 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " .
|
569 |
+
"/" . Paths::getWodUrlPath() .
|
570 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
571 |
+
" [" . implode(',', $flags) . "]\n";
|
572 |
+
*/
|
573 |
+
|
574 |
+
/*
|
575 |
+
TODO: NO, this will not do on systems that cannot pass through ENV.
|
576 |
+
(Or is REQUEST_FILENAME useable at all? If it is, then we could perhaps
|
577 |
+
catch the whole %{REQUEST_FILENAME} and pass it in %1)
|
578 |
+
|
579 |
+
$params = [];
|
580 |
+
$flags = ['NC', 'L'];
|
581 |
+
|
582 |
+
if ($passRelativePathToSourceInQS) {
|
583 |
+
$params[] = 'xsource-rel-to-plugin-dir=x' . self::$htaccessDirRelToDocRoot . '/$1.$2';
|
584 |
+
}
|
585 |
+
if (!self::$passThroughEnvVarDefinitelyAvailable) {
|
586 |
+
$params[] = "xwp-content-rel-to-plugin-dir=x" . Paths::getContentDirRelToPluginDir();
|
587 |
+
}
|
588 |
+
|
589 |
+
// $rules .= " RewriteCond %{REQUEST_FILENAME} -f\n";
|
590 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(" .
|
591 |
+
self::$htaccessDirAbs . "/)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
592 |
+
|
593 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " .
|
594 |
+
"/" . Paths::getWodUrlPath() .
|
595 |
+
(count($params) > 0 ? "?" . implode('&', $params) : "") .
|
596 |
+
" [" . implode(',', $flags) . "]\n";
|
597 |
+
|
598 |
+
//$urlPath = '/' . Paths::getUrlPathById('plugins') . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
599 |
+
// $urlPath = '/' . Paths::getUrlPathById(self::$htaccessDir) . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
600 |
+
//$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
601 |
+
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
602 |
+
//$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath ." [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
603 |
+
*/
|
604 |
+
|
605 |
+
|
606 |
+
|
607 |
+
|
608 |
+
|
609 |
+
}
|
610 |
+
|
611 |
+
|
612 |
+
/*
|
613 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(" . self::$htaccessDirAbs . "/)(.*)(" . self::$fileExtIncludingDot . ")$\n";
|
614 |
+
$urlPath = '/' . Paths::getContentUrlPath() . "/webp-express/webp-images/" . self::$htaccessDir . "/%2" . (self::$appendWebP ? "%3" : "") . "\.webp";
|
615 |
+
//$rules .= " RewriteCond %1" . (self::$appendWebP ? "%2" : "") . "\.webp -f\n";
|
616 |
+
$rules .= " RewriteRule (?i)(.*)(" . self::$fileExtIncludingDot . ")$ " . $urlPath .
|
617 |
+
" [T=image/webp,E=EXISTING:1," . (self::$addVary ? 'E=ADDVARY:1,' : '') . "L]\n\n";
|
618 |
+
*/
|
619 |
+
|
620 |
+
/*
|
621 |
+
$rules .= " RewriteCond %{REQUEST_FILENAME} (?i)(.*)(\.jpe?g|\.png)$\n";
|
622 |
+
$rules .= " RewriteCond %1%2\.webp -f\n";
|
623 |
+
$rules .= " RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1%2\.webp [T=image/webp,E=EXISTING:1,E=ADDVARY:1,L]\n";
|
624 |
+
*/
|
625 |
+
|
626 |
+
$rules .= "\n";
|
627 |
+
return $rules;
|
628 |
+
}
|
629 |
+
|
630 |
+
private static function setInternalProperties($config, $htaccessDir = 'index')
|
631 |
+
{
|
632 |
+
self::$useDocRootForStructuringCacheDir = (($config['destination-structure'] == 'doc-root') && Paths::canUseDocRootForStructuringCacheDir());
|
633 |
+
self::$htaccessDir = $htaccessDir;
|
634 |
+
self::$htaccessDirAbs = Paths::getAbsDirById(self::$htaccessDir);
|
635 |
+
|
636 |
+
self::$htaccessDirRelToDocRoot = '';
|
637 |
+
if (self::$useDocRootForStructuringCacheDir) {
|
638 |
+
self::$htaccessDirRelToDocRoot = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(
|
639 |
+
self::$htaccessDirAbs
|
640 |
+
);
|
641 |
+
}
|
642 |
+
|
643 |
+
// When using the absolute dir, the rewrite rules needs document root and does not work
|
644 |
+
// if the symlinks have been resolved.
|
645 |
+
// We can fix this - but only if document root is available and resolvable.
|
646 |
+
// - which is sad, because the image-roots was introduced in order to get it to work on setups
|
647 |
+
// where it isn't.
|
648 |
+
self::$htaccessDirAbs = PathHelper::fixAbsPathToUseUnresolvedDocRoot(self::$htaccessDirAbs);
|
649 |
+
|
650 |
+
// Fix config.
|
651 |
+
$defaults = [
|
652 |
+
'enable-redirection-to-converter' => true,
|
653 |
+
'forward-query-string' => true,
|
654 |
+
'image-types' => 1,
|
655 |
+
'do-not-pass-source-in-query-string' => false,
|
656 |
+
'redirect-to-existing-in-htaccess' => false,
|
657 |
+
'only-redirect-to-converter-on-cache-miss' => false,
|
658 |
+
'destination-folder' => 'separate',
|
659 |
+
'destination-extension' => 'append',
|
660 |
+
'destination-structure' => 'doc-root',
|
661 |
+
'success-response' => 'converted',
|
662 |
+
];
|
663 |
+
$config = array_merge($defaults, $config);
|
664 |
+
|
665 |
+
if (!isset($config['base-htaccess-on-these-capability-tests'])) {
|
666 |
+
$config['base-htaccess-on-these-capability-tests'] = Config::runAndStoreCapabilityTests();
|
667 |
+
}
|
668 |
+
self::$config = $config;
|
669 |
+
|
670 |
+
$capTests = self::$config['base-htaccess-on-these-capability-tests'];
|
671 |
+
self::$modHeaderDefinitelyUnavailable = ($capTests['modHeaderWorking'] === false);
|
672 |
+
self::$passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
|
673 |
+
self::$passThroughHeaderDefinitelyAvailable = ($capTests['passThroughHeaderWorking'] === true);
|
674 |
+
self::$passThroughEnvVarDefinitelyUnavailable = ($capTests['passThroughEnvWorking'] === false);
|
675 |
+
self::$passThroughEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
|
676 |
+
self::$capTests = $capTests;
|
677 |
+
|
678 |
+
self::$imageTypes = self::$config['image-types'];
|
679 |
+
self::$fileExtensions = [];
|
680 |
+
if (self::$imageTypes & 1) {
|
681 |
+
self::$fileExtensions[] = 'jpe?g';
|
682 |
+
}
|
683 |
+
if (self::$imageTypes & 2) {
|
684 |
+
self::$fileExtensions[] = 'png';
|
685 |
+
}
|
686 |
+
self::$fileExt = implode('|', self::$fileExtensions);
|
687 |
+
self::$fileExtIncludingDot = "\." . implode("|\.", self::$fileExtensions);
|
688 |
+
|
689 |
+
self::$mingled = (self::$config['destination-folder'] == 'mingled');
|
690 |
+
|
691 |
+
// TODO: If we cannot store all .htaccess files we would like, we need to take into account which dir
|
692 |
+
$setWebPExt = ((self::$config['destination-extension'] == 'set') && (self::$htaccessDir == 'uploads'));
|
693 |
+
self::$appendWebP = !$setWebPExt;
|
694 |
+
}
|
695 |
+
|
696 |
+
public static function addVaryHeaderEnvRules($indent = 0)
|
697 |
+
{
|
698 |
+
$rules = [];
|
699 |
+
$rules[] = "# Set Vary:Accept header if we came here by way of our redirect, which set the ADDVARY environment variable";
|
700 |
+
$rules[] = "# The purpose is to make proxies and CDNs aware that the response varies with the Accept header";
|
701 |
+
$rules[] = "<IfModule mod_headers.c>";
|
702 |
+
$rules[] = " <IfModule mod_setenvif.c>";
|
703 |
+
$rules[] = " # Apache appends \"REDIRECT_\" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not";
|
704 |
+
$rules[] = " # So, the next lines are for Apache, in order to set environment variables without \"REDIRECT_\"";
|
705 |
+
$rules[] = " SetEnvIf REDIRECT_EXISTING 1 EXISTING=1";
|
706 |
+
$rules[] = " SetEnvIf REDIRECT_ADDVARY 1 ADDVARY=1";
|
707 |
+
$rules[] = "";
|
708 |
+
$rules[] = " Header append \"Vary\" \"Accept\" env=ADDVARY";
|
709 |
+
$rules[] = "";
|
710 |
+
|
711 |
+
if (self::$config['redirect-to-existing-in-htaccess']) {
|
712 |
+
$rules[] = " # Set X-WebP-Express header for diagnose purposes";
|
713 |
+
//" SetEnvIf REDIRECT_WOD 1 WOD=1\n\n" .
|
714 |
+
//" # Set the debug header\n" .
|
715 |
+
$rules[] = " Header set \"X-WebP-Express\" \"Redirected directly to existing webp\" env=EXISTING";
|
716 |
+
//" Header set \"X-WebP-Express\" \"Redirected to image converter\" env=WOD\n" .
|
717 |
+
}
|
718 |
+
$rules[] = " </IfModule>";
|
719 |
+
$rules[] = "</IfModule>";
|
720 |
+
$rules[] = "";
|
721 |
+
|
722 |
+
if ($indent > 0) {
|
723 |
+
$indentStr = '';
|
724 |
+
for ($x=0; $x<$indent; $x++) {
|
725 |
+
$indentStr .= ' ';
|
726 |
+
}
|
727 |
+
foreach ($rules as $i => $rule) {
|
728 |
+
if ($rule != '') {
|
729 |
+
$rules[$i] = $indentStr . $rule;
|
730 |
+
}
|
731 |
+
}
|
732 |
+
}
|
733 |
+
return implode("\n", $rules);
|
734 |
+
}
|
735 |
+
|
736 |
+
// https://stackoverflow.com/questions/34124819/mod-rewrite-set-custom-header-through-htaccess
|
737 |
+
public static function generateHTAccessRulesFromConfigObj($config, $htaccessDir = 'index', $dirContainsSourceImages = true, $dirContainsWebPImages = true)
|
738 |
+
{
|
739 |
+
self::setInternalProperties($config, $htaccessDir);
|
740 |
+
self::$dirContainsSourceImages = $dirContainsSourceImages;
|
741 |
+
self::$dirContainsWebPImages = $dirContainsWebPImages;
|
742 |
+
|
743 |
+
if (
|
744 |
+
(!self::$config['enable-redirection-to-converter']) &&
|
745 |
+
(!self::$config['redirect-to-existing-in-htaccess']) &&
|
746 |
+
(!self::$config['enable-redirection-to-webp-realizer'])
|
747 |
+
) {
|
748 |
+
return '# WebP Express does not need to write any rules (it has not been set up to redirect to converter, nor' .
|
749 |
+
' to existing webp, and the "convert non-existing webp-files upon request" option has not been enabled)';
|
750 |
+
}
|
751 |
+
|
752 |
+
if (self::$imageTypes == 0) {
|
753 |
+
return '# WebP Express disabled (no image types has been choosen to be converted/redirected)';
|
754 |
+
}
|
755 |
+
|
756 |
+
self::$addVary = self::$config['redirect-to-existing-in-htaccess'];
|
757 |
+
if (self::$modHeaderDefinitelyUnavailable) {
|
758 |
+
self::$addVary = false;
|
759 |
+
}
|
760 |
+
|
761 |
+
/* Build rules */
|
762 |
+
$rules = '';
|
763 |
+
$rules .= self::infoRules();
|
764 |
+
|
765 |
+
if ($dirContainsSourceImages) {
|
766 |
+
$rules .= "# Rules for handling requests for source images\n";
|
767 |
+
$rules .= "# ---------------------------------------------\n\n";
|
768 |
+
$rules .= "<IfModule mod_rewrite.c>\n" .
|
769 |
+
" RewriteEngine On\n\n";
|
770 |
+
|
771 |
+
if (self::$config['redirect-to-existing-in-htaccess']) {
|
772 |
+
$rules .= self::redirectToExistingRules();
|
773 |
+
}
|
774 |
+
|
775 |
+
if (self::$config['enable-redirection-to-converter']) {
|
776 |
+
$rules .= self::webpOnDemandRules();
|
777 |
+
}
|
778 |
+
|
779 |
+
//if (self::$addVary) {
|
780 |
+
if (
|
781 |
+
(self::$config['redirect-to-existing-in-htaccess']) ||
|
782 |
+
(self::$config['enable-redirection-to-converter'])
|
783 |
+
) {
|
784 |
+
$rules .= " # Make sure that browsers which does not support webp also gets the Vary:Accept header\n" .
|
785 |
+
" # when requesting images that would be redirected to webp on browsers that does.\n";
|
786 |
+
|
787 |
+
$rules .= " <IfModule mod_headers.c>\n";
|
788 |
+
$rules .= ' <FilesMatch "\.(jpe?g|png)$">' . "\n";
|
789 |
+
$rules .= ' Header append "Vary" "Accept"' . "\n";
|
790 |
+
$rules .= " </FilesMatch>\n";
|
791 |
+
$rules .= " </IfModule>\n\n";
|
792 |
+
}
|
793 |
+
|
794 |
+
/*
|
795 |
+
" <IfModule mod_setenvif.c>\n" .
|
796 |
+
" SetEnvIf Request_URI \"\.(" . self::$fileExt . ")$\" ADDVARY\n" .
|
797 |
+
" </IfModule>\n\n";
|
798 |
+
*/
|
799 |
+
|
800 |
+
//self::$addVary = (self::$config['enable-redirection-to-converter'] && (self::$config['success-response'] == 'converted')) || (self::$config['redirect-to-existing-in-htaccess']);
|
801 |
+
|
802 |
+
/*
|
803 |
+
if (self::$addVary) {
|
804 |
+
if ($dirContainsWebPImages) {
|
805 |
+
$rules .= self::addVaryHeaderEnvRules(2);
|
806 |
+
}
|
807 |
+
}*/
|
808 |
+
$rules .= "</IfModule>\n";
|
809 |
+
} /*else {
|
810 |
+
if ($dirContainsWebPImages) {
|
811 |
+
$rules .= self::addVaryHeaderEnvRules();
|
812 |
+
}
|
813 |
+
}*/
|
814 |
+
if ($dirContainsWebPImages) {
|
815 |
+
$rules .= "\n# Rules for handling requests for webp images\n";
|
816 |
+
$rules .= "# ---------------------------------------------\n\n";
|
817 |
+
if (self::$config['enable-redirection-to-webp-realizer']) {
|
818 |
+
$rules .= self::webpRealizerRules();
|
819 |
+
}
|
820 |
+
$rules .= self::cacheRules();
|
821 |
+
|
822 |
+
/*
|
823 |
+
if (
|
824 |
+
(self::$config['enable-redirection-to-webp-realizer']) ||
|
825 |
+
(self::$config['redirect-to-existing-in-htaccess'])
|
826 |
+
) {
|
827 |
+
}*/
|
828 |
+
$rules .= self::addVaryHeaderEnvRules();
|
829 |
+
|
830 |
+
$rules .= "\n# Register webp mime type \n";
|
831 |
+
$rules .= "<IfModule mod_mime.c>\n";
|
832 |
+
$rules .= " AddType image/webp .webp\n";
|
833 |
+
$rules .= "</IfModule>\n";
|
834 |
+
}
|
835 |
+
|
836 |
+
/*if (self::$config['redirect-to-existing-in-htaccess']) {
|
837 |
+
$rules .=
|
838 |
+
"<IfModule mod_headers.c>\n" .
|
839 |
+
" # Append Vary Accept header, when the rules above are redirecting to existing webp\n" .
|
840 |
+
" # or existing jpg" .
|
841 |
+
|
842 |
+
" # Apache appends \"REDIRECT_\" in front of the environment variables, but LiteSpeed does not.\n" .
|
843 |
+
" # These next line is for Apache, in order to set environment variables without \"REDIRECT_\"\n" .
|
844 |
+
" SetEnvIf REDIRECT_WEBPACCEPT 1 WEBPACCEPT=1\n\n" .
|
845 |
+
|
846 |
+
" # Make CDN caching possible.\n" .
|
847 |
+
" # The effect is that the CDN will cache both the webp image and the jpeg/png image and return the proper\n" .
|
848 |
+
" # image to the proper clients (for this to work, make sure to set up CDN to forward the \"Accept\" header)\n" .
|
849 |
+
" Header append Vary Accept env=WEBPACCEPT\n" .
|
850 |
+
"</IfModule>\n\n";
|
851 |
+
}*/
|
852 |
+
|
853 |
+
return $rules;
|
854 |
+
}
|
855 |
+
}
|
lib/classes/HandleUploadHooks.php
CHANGED
@@ -40,7 +40,7 @@ class HandleUploadHooks
|
|
40 |
$imageTypes = $config['image-types'];
|
41 |
if ($imageTypes & 1) {
|
42 |
$allowedMimeTypes[] = 'image/jpeg';
|
43 |
-
$allowedMimeTypes[] = 'image/jpg'; /* don't think "image/jpg" is
|
44 |
}
|
45 |
if ($imageTypes & 2) {
|
46 |
$allowedMimeTypes[] = 'image/png';
|
40 |
$imageTypes = $config['image-types'];
|
41 |
if ($imageTypes & 1) {
|
42 |
$allowedMimeTypes[] = 'image/jpeg';
|
43 |
+
$allowedMimeTypes[] = 'image/jpg'; /* don't think "image/jpg" is necessary, but just in case */
|
44 |
}
|
45 |
if ($imageTypes & 2) {
|
46 |
$allowedMimeTypes[] = 'image/png';
|
lib/classes/ImageRoot.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
use \WebPExpress\PathHelper;
|
6 |
+
|
7 |
+
class ImageRoot
|
8 |
+
{
|
9 |
+
/**
|
10 |
+
* Constructor.
|
11 |
+
*
|
12 |
+
* @param array $imageRootDef assoc array containing "id", "url" and either "abs-path", "rel-path" or both.
|
13 |
+
*/
|
14 |
+
public function __construct($imageRootDef)
|
15 |
+
{
|
16 |
+
$this->imageRootDef = $imageRootDef;
|
17 |
+
$this->id = $imageRootDef['id'];
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Get / calculate abs path.
|
22 |
+
*
|
23 |
+
* If "rel-path" is set and document root is available, the abs path will be calculated from the relative path.
|
24 |
+
* Otherwise the "abs-path" is returned.
|
25 |
+
* @throws Exception In case rel-path is not
|
26 |
+
*/
|
27 |
+
public function getAbsPath()
|
28 |
+
{
|
29 |
+
$def = $this->imageRootDef;
|
30 |
+
if (isset($def['rel-path']) && PathHelper::isDocRootAvailable()) {
|
31 |
+
return rtrim($_SERVER["DOCUMENT_ROOT"], '/') . '/' . $def['rel-path'];
|
32 |
+
} elseif (isset($def['abs-path'])) {
|
33 |
+
return $def['abs-path'];
|
34 |
+
} else {
|
35 |
+
if (!isset($def['rel-path'])) {
|
36 |
+
throw new \Exception(
|
37 |
+
'Image root definition in config file is must either have a "rel-path" or "abs-path" property defined. ' .
|
38 |
+
'Probably your system setup has changed. Please re-save WebP Express options and regenerate .htaccess'
|
39 |
+
);
|
40 |
+
} else {
|
41 |
+
throw new \Exception(
|
42 |
+
'Image root definition in config file is defined by "rel-path". However, DOCUMENT_ROOT is unavailable so we ' .
|
43 |
+
'cannot use that (as the rel-path is relative to that. ' .
|
44 |
+
'Probably your system setup has changed. Please re-save WebP Express options and regenerate .htaccess'
|
45 |
+
);
|
46 |
+
}
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
}
|
lib/classes/ImageRoots.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
use \WebPExpress\ImageRoot;
|
6 |
+
|
7 |
+
class ImageRoots
|
8 |
+
{
|
9 |
+
/**
|
10 |
+
* Constructor.
|
11 |
+
*
|
12 |
+
* @param array $imageRoots Array representation of image roots
|
13 |
+
*/
|
14 |
+
public function __construct($imageRootsDef)
|
15 |
+
{
|
16 |
+
$this->imageRootsDef = $imageRootsDef;
|
17 |
+
|
18 |
+
$this->imageRoots = [];
|
19 |
+
foreach ($imageRootsDef as $i => $def)
|
20 |
+
{
|
21 |
+
$this->imageRoots[] = new ImageRoot($def);
|
22 |
+
}
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Get image root by id.
|
27 |
+
*
|
28 |
+
* @return \WebPExpress\ImageRoot An image root object
|
29 |
+
*/
|
30 |
+
public function byId($id)
|
31 |
+
{
|
32 |
+
foreach ($this->imageRoots as $i => $imageRoot) {
|
33 |
+
if ($imageRoot->id == $id) {
|
34 |
+
return $imageRoot;
|
35 |
+
}
|
36 |
+
}
|
37 |
+
throw new \Exception('Image root not found');
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Get the image roots array
|
42 |
+
*
|
43 |
+
* @return array An array of ImageRoot objects
|
44 |
+
*/
|
45 |
+
public function getArray()
|
46 |
+
{
|
47 |
+
return $this->imageRoots;
|
48 |
+
}
|
49 |
+
}
|
lib/classes/Mime.php
CHANGED
@@ -10,10 +10,15 @@ class Mime
|
|
10 |
|
11 |
public static function getMimeTypeOfMedia($filename)
|
12 |
{
|
13 |
-
//
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
17 |
}
|
18 |
|
19 |
// Try mime_content_type
|
@@ -24,10 +29,12 @@ class Mime
|
|
24 |
}
|
25 |
}
|
26 |
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
31 |
}
|
32 |
|
33 |
// Don't say we didn't try!
|
10 |
|
11 |
public static function getMimeTypeOfMedia($filename)
|
12 |
{
|
13 |
+
// First try the Wordpress function if available (it was introduced in 4.7.1)
|
14 |
+
if (function_exists('wp_get_image_mime')) {
|
15 |
+
|
16 |
+
// PS: wp_get_image_mime tries exif_imagetype and getimagesize and returns false if no methods are available
|
17 |
+
$mimeType = wp_get_image_mime($filename);
|
18 |
+
if ($mimeType !== false) {
|
19 |
+
return $mimeType;
|
20 |
+
}
|
21 |
+
|
22 |
}
|
23 |
|
24 |
// Try mime_content_type
|
29 |
}
|
30 |
}
|
31 |
|
32 |
+
if (function_exists('wp_check_filetype')) { // introduced in 2.0.4
|
33 |
+
// Try wordpress method, which simply uses the file extension and a map
|
34 |
+
$mimeType = wp_check_filetype($filePath)['type'];
|
35 |
+
if ($mimeType !== false) {
|
36 |
+
return $mimeType;
|
37 |
+
}
|
38 |
}
|
39 |
|
40 |
// Don't say we didn't try!
|
lib/classes/PathHelper.php
CHANGED
@@ -5,6 +5,333 @@ namespace WebPExpress;
|
|
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.
|
@@ -43,6 +370,23 @@ class PathHelper
|
|
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)
|
5 |
class PathHelper
|
6 |
{
|
7 |
|
8 |
+
public static function isDocRootAvailable() {
|
9 |
+
|
10 |
+
//return false;
|
11 |
+
|
12 |
+
if (!isset($_SERVER['DOCUMENT_ROOT'])) {
|
13 |
+
return false;
|
14 |
+
}
|
15 |
+
if ($_SERVER['DOCUMENT_ROOT'] == '') {
|
16 |
+
return false;
|
17 |
+
}
|
18 |
+
return true;
|
19 |
+
}
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Test if a path exists as is resolvable (will be unless it is outside open_basedir)
|
23 |
+
*
|
24 |
+
* @param string $absPath The path to test (must be absolute. symlinks allowed)
|
25 |
+
* @return boolean The result
|
26 |
+
*/
|
27 |
+
public static function pathExistsAndIsResolvable($absPath) {
|
28 |
+
if (!@realpath($absPath)) {
|
29 |
+
return false;
|
30 |
+
}
|
31 |
+
return true;
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Test if document root is available, exists and symlinks are resolvable (resolved path is within open basedir)
|
36 |
+
*
|
37 |
+
* @return boolean The result
|
38 |
+
*/
|
39 |
+
public static function isDocRootAvailableAndResolvable() {
|
40 |
+
return (
|
41 |
+
self::isDocRootAvailable() &&
|
42 |
+
self::pathExistsAndIsResolvable($_SERVER['DOCUMENT_ROOT'])
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
public static function fixAbsPathToUseUnresolvedDocRoot($absPath) {
|
47 |
+
if (self::isDocRootAvailableAndResolvable()) {
|
48 |
+
if (strpos($absPath, realpath($_SERVER['DOCUMENT_ROOT'])) === 0) {
|
49 |
+
return $_SERVER['DOCUMENT_ROOT'] . substr($absPath, strlen(realpath($_SERVER['DOCUMENT_ROOT'])));
|
50 |
+
}
|
51 |
+
}
|
52 |
+
return $absPath;
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Find out if path is below - or equal to a path.
|
57 |
+
*
|
58 |
+
* "/var/www" below/equal to "/var"? : Yes
|
59 |
+
* "/var/www" below/equal to "/var/www"? : Yes
|
60 |
+
* "/var/www2" below/equal to "/var/www"? : No
|
61 |
+
*/
|
62 |
+
/*
|
63 |
+
public static function isPathBelowOrEqualToPath($path1, $path2)
|
64 |
+
{
|
65 |
+
return (strpos($path1 . '/', $path2 . '/') === 0);
|
66 |
+
//$rel = self::getRelDir($path2, $path1);
|
67 |
+
//return (substr($rel, 0, 3) != '../');
|
68 |
+
}*/
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Calculate relative path from document root to a given absolute path (must exist and be resolvable) - if possible AND
|
72 |
+
* if it can be done without directory traversal.
|
73 |
+
*
|
74 |
+
* The function is designed with the usual folders in mind (index, uploads, wp-content, plugins), which all presumably
|
75 |
+
* exists and are within open_basedir.
|
76 |
+
*
|
77 |
+
* @param string $dir An absolute path (may contain symlinks). The path must exist and be resolvable.
|
78 |
+
* @throws \Exception If it is not possible to get such path (ie if doc-root is unavailable or the dir is outside doc-root)
|
79 |
+
* @return string Relative path to document root or empty string if document root is unavailable
|
80 |
+
*/
|
81 |
+
public static function getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($dir)
|
82 |
+
{
|
83 |
+
if (!self::isDocRootAvailable()) {
|
84 |
+
throw new \Exception('Cannot calculate relative path from document root to dir, as document root is not available');
|
85 |
+
}
|
86 |
+
|
87 |
+
// First try unresolved.
|
88 |
+
// This will even work when ie wp-content is symlinked to somewhere outside document root, while the symlink itself is within document root)
|
89 |
+
$relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], $dir);
|
90 |
+
if (strpos($relPath, '../') !== 0) { // Check if relPath starts with "../" (if it does, we cannot use it)
|
91 |
+
return $relPath;
|
92 |
+
}
|
93 |
+
|
94 |
+
if (self::isDocRootAvailableAndResolvable()) {
|
95 |
+
if (self::pathExistsAndIsResolvable($dir)) {
|
96 |
+
// Try with both resolved
|
97 |
+
$relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), realpath($dir));
|
98 |
+
if (strpos($relPath, '../') !== 0) {
|
99 |
+
return $relPath;
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
// Try with just document root resolved
|
104 |
+
$relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir);
|
105 |
+
if (strpos($relPath, '../') !== 0) {
|
106 |
+
return $relPath;
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
if (self::pathExistsAndIsResolvable($dir)) {
|
111 |
+
// Try with dir resolved
|
112 |
+
$relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], realpath($dir));
|
113 |
+
if (strpos($relPath, '../') !== 0) {
|
114 |
+
return $relPath;
|
115 |
+
}
|
116 |
+
}
|
117 |
+
|
118 |
+
// Problem:
|
119 |
+
// - dir is already resolved (ie: /disk/the-content)
|
120 |
+
// - document root is ie. /var/www/website/wordpress
|
121 |
+
// - the unresolved symlink is ie. /var/www/website/wordpress/wp-content
|
122 |
+
// - we do not know what the unresolved symlink is
|
123 |
+
// The result should be "wp-content". But how do we get to that result?
|
124 |
+
// I guess we must check out all folders below document root to see if anyone resolves to dir
|
125 |
+
// we could start out trying usual suspects such as "wp-content" and "wp-content/uploads"
|
126 |
+
//foreach (glob($dir . DIRECTORY_SEPARATOR . $filePattern) as $filename)
|
127 |
+
/*
|
128 |
+
$iter = new \RecursiveIteratorIterator(
|
129 |
+
new \RecursiveDirectoryIterator($_SERVER['DOCUMENT_ROOT'], \RecursiveDirectoryIterator::SKIP_DOTS),
|
130 |
+
\RecursiveIteratorIterator::SELF_FIRST,
|
131 |
+
\RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied"
|
132 |
+
);
|
133 |
+
|
134 |
+
foreach ($iter as $path => $dirObj) {
|
135 |
+
if ($dirObj->isDir()) {
|
136 |
+
if (realpath($path) == $dir) {
|
137 |
+
//return $path;
|
138 |
+
$relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $path);
|
139 |
+
if (strpos($relPath, '../') !== 0) {
|
140 |
+
return $relPath;
|
141 |
+
}
|
142 |
+
}
|
143 |
+
}
|
144 |
+
}
|
145 |
+
*/
|
146 |
+
// Ok, the above works - but when subfolders to the symlink is referenced. Ie referencing uploads when wp-content is symlinked
|
147 |
+
// - dir is already resolved (ie: /disk/the-content/uploads)
|
148 |
+
// - document root is ie. /var/www/website/wordpress
|
149 |
+
// - the unresolved symlink is ie. /var/www/website/wordpress/wp-content/uploads
|
150 |
+
// - we do not know what the unresolved symlink is
|
151 |
+
// The result should be "wp-content/uploads". But how do we get to that result?
|
152 |
+
|
153 |
+
// What if we collect all symlinks below document root in a assoc array?
|
154 |
+
// ['/disk/the-content' => 'wp-content']
|
155 |
+
// Input is: '/disk/the-content/uploads'
|
156 |
+
// 1. We check the symlinks and substitute. We get: 'wp-content/uploads'.
|
157 |
+
// 2. We test if realpath($_SERVER['DOCUMENT_ROOT'] . '/' . 'wp-content/uploads') equals input.
|
158 |
+
// It seems I have a solution!
|
159 |
+
// - I shall continue work soon! - for a 0.15.1 release (test instance #26)
|
160 |
+
// PS: cache the result of the symlinks in docroot collector.
|
161 |
+
|
162 |
+
throw new \Exception(
|
163 |
+
'Cannot get relative path from document root to dir without resolving to directory traversal. ' .
|
164 |
+
'It seems the dir is not below document root'
|
165 |
+
);
|
166 |
+
|
167 |
+
/*
|
168 |
+
if (!self::pathExistsAndIsResolvable($dir)) {
|
169 |
+
throw new \Exception('Cannot calculate relative path from document root to dir. The path given is not resolvable (realpath fails)');
|
170 |
+
}
|
171 |
+
|
172 |
+
|
173 |
+
// Check if relPath starts with "../"
|
174 |
+
if (strpos($relPath, '../') === 0) {
|
175 |
+
|
176 |
+
// Unresolved failed. Try with document root resolved
|
177 |
+
$relPath = self::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir);
|
178 |
+
|
179 |
+
if (strpos($relPath, '../') === 0) {
|
180 |
+
|
181 |
+
// Try with both resolved
|
182 |
+
$relPath = self::getRelDir($dir, $dir);
|
183 |
+
throw new \Exception('Cannot calculate relative path from document root to dir. The path given is not within document root');
|
184 |
+
}
|
185 |
+
}
|
186 |
+
|
187 |
+
|
188 |
+
}
|
189 |
+
|
190 |
+
return $relPath;
|
191 |
+
} else {
|
192 |
+
// We cannot get the resolved doc-root.
|
193 |
+
// This might be ok as long as the (resolved) path we are examining begins with the configured doc-root.
|
194 |
+
$relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], $dir);
|
195 |
+
|
196 |
+
// Check if relPath starts with "../" (it may not)
|
197 |
+
if (strpos($relPath, '../') === 0) {
|
198 |
+
|
199 |
+
// Well, that did not work. We can try the resolved path instead.
|
200 |
+
if (!self::pathExistsAndIsResolvable($dir)) {
|
201 |
+
throw new \Exception('Cannot calculate relative path from document root to dir. The path given is not resolvable (realpath fails)');
|
202 |
+
}
|
203 |
+
|
204 |
+
$relPath = self::getRelDir($_SERVER['DOCUMENT_ROOT'], realpath($dir));
|
205 |
+
if (strpos($relPath, '../') === 0) {
|
206 |
+
|
207 |
+
// That failed too.
|
208 |
+
// Either it is in fact outside document root or it is because of a special setup.
|
209 |
+
throw new \Exception(
|
210 |
+
'Cannot calculate relative path from document root to dir. Either the path given is not within the configured document root or ' .
|
211 |
+
'it is because of a special setup. The document root is outside open_basedir. If it is also symlinked, but the other Wordpress paths ' .
|
212 |
+
'are not using that same symlink, it will not be possible to calculate the relative path.'
|
213 |
+
);
|
214 |
+
}
|
215 |
+
}
|
216 |
+
return $relPath;
|
217 |
+
}*/
|
218 |
+
}
|
219 |
+
|
220 |
+
public static function canCalculateRelPathFromDocRootToDir($dir)
|
221 |
+
{
|
222 |
+
try {
|
223 |
+
$relPath = self::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($dir);
|
224 |
+
} catch (\Exception $e) {
|
225 |
+
return false;
|
226 |
+
}
|
227 |
+
return true;
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Find closest existing folder with symlinks expandend, using realpath.
|
232 |
+
*
|
233 |
+
* Note that if the input or the closest existing folder is outside open_basedir, no folder will
|
234 |
+
* be found and an empty string will be returned.
|
235 |
+
*
|
236 |
+
* @return string closest existing path or empty string if none found (due to open_basedir restriction)
|
237 |
+
*/
|
238 |
+
public static function findClosestExistingFolderSymLinksExpanded($input) {
|
239 |
+
|
240 |
+
// The strategy is to first try the supplied directory. If it fails, try the parent, etc.
|
241 |
+
$dir = $input;
|
242 |
+
|
243 |
+
// We count the levels up to avoid infinite loop - as good practice. It ought not to get that far
|
244 |
+
$levelsUp = 0;
|
245 |
+
|
246 |
+
while ($levelsUp < 100) {
|
247 |
+
// We suppress warning because we are aware that we might get a
|
248 |
+
// open_basedir restriction warning.
|
249 |
+
$realPathResult = @realpath($dir);
|
250 |
+
if ($realPathResult !== false) {
|
251 |
+
return $realPathResult;
|
252 |
+
}
|
253 |
+
// Stop at root. This will happen if the original path is outside basedir.
|
254 |
+
if (($dir == '/') || (strlen($dir) < 4)) {
|
255 |
+
return '';
|
256 |
+
}
|
257 |
+
// Peal off one directory
|
258 |
+
$dir = @dirname($dir);
|
259 |
+
$levelsUp++;
|
260 |
+
}
|
261 |
+
return '';
|
262 |
+
}
|
263 |
+
|
264 |
+
/**
|
265 |
+
* Look if filepath is within a dir path (both by string matching and by using realpath, see notes).
|
266 |
+
*
|
267 |
+
* Note that the naive string match does not resolve '..'. You might want to call ::canonicalize first.
|
268 |
+
* Note that the realpath match requires: 1. that the dir exist and is within open_basedir
|
269 |
+
* 2. that the closest existing folder within filepath is within open_basedir
|
270 |
+
*
|
271 |
+
* @param string $filePath Path to file. It may be non-existing.
|
272 |
+
* @param string $dirPath Path to dir. It must exist and be within open_basedir in order for the realpath match to execute.
|
273 |
+
*/
|
274 |
+
public static function isFilePathWithinDirPath($filePath, $dirPath)
|
275 |
+
{
|
276 |
+
// See if $filePath begins with $dirPath + '/'.
|
277 |
+
if (strpos($filePath, $dirPath . '/') === 0) {
|
278 |
+
return true;
|
279 |
+
}
|
280 |
+
|
281 |
+
if (strpos(self::canonicalize($filePath), self::canonicalize($dirPath) . '/') === 0) {
|
282 |
+
return true;
|
283 |
+
}
|
284 |
+
|
285 |
+
|
286 |
+
// Also try with symlinks expanded.
|
287 |
+
// As symlinks can only be retrieved with realpath and realpath fails with non-existing paths,
|
288 |
+
// we settle with checking if closest existing folder in the filepath is within the dir.
|
289 |
+
// If that is the case, then surely, the complete filepath is also within the dir.
|
290 |
+
// Note however that it might be that the closest existing folder is not within the dir, while the
|
291 |
+
// file would be (if it existed)
|
292 |
+
// For WebP Express, we are pretty sure that the dirs we are checking against (uploads folder,
|
293 |
+
// wp-content, plugins folder) exists. So getting the closest existing folder should be sufficient.
|
294 |
+
// but could it be that these are outside open_basedir on some setups? Perhaps on a few systems.
|
295 |
+
if (self::pathExistsAndIsResolvable($dirPath)) {
|
296 |
+
$closestExistingDirOfFile = PathHelper::findClosestExistingFolderSymLinksExpanded($filePath);
|
297 |
+
if (strpos($closestExistingDirOfFile, realpath($dirPath) . '/') === 0) {
|
298 |
+
return true;
|
299 |
+
}
|
300 |
+
}
|
301 |
+
|
302 |
+
return false;
|
303 |
+
}
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Look if path is within a dir path. Also tries expanding symlinks
|
307 |
+
*
|
308 |
+
* @param string $path Path to examine. It may be non-existing.
|
309 |
+
* @param string $dirPath Path to dir. It must exist in order for symlinks to be expanded.
|
310 |
+
*/
|
311 |
+
public static function isPathWithinExistingDirPath($path, $dirPath)
|
312 |
+
{
|
313 |
+
if ($path == $dirPath) {
|
314 |
+
return true;
|
315 |
+
}
|
316 |
+
// See if $filePath begins with $dirPath + '/'.
|
317 |
+
if (strpos($path, $dirPath . '/') === 0) {
|
318 |
+
return true;
|
319 |
+
}
|
320 |
+
|
321 |
+
// Also try with symlinks expanded (see comments in ::isFilePathWithinDirPath())
|
322 |
+
$closestExistingDir = PathHelper::findClosestExistingFolderSymLinksExpanded($path);
|
323 |
+
if (strpos($closestExistingDir . '/', $dirPath . '/') === 0) {
|
324 |
+
return true;
|
325 |
+
}
|
326 |
+
return false;
|
327 |
+
}
|
328 |
+
|
329 |
+
public static function frontslasher($str)
|
330 |
+
{
|
331 |
+
// TODO: replace backslash with frontslash
|
332 |
+
return $str;
|
333 |
+
}
|
334 |
+
|
335 |
/**
|
336 |
* Replace double slash with single slash. ie '/var//www/' => '/var/www/'
|
337 |
* This allows you to lazely concatenate paths with '/' and then call this method to clean up afterwards.
|
370 |
return implode('/', $parts);
|
371 |
}
|
372 |
|
373 |
+
public static function dirname($path) {
|
374 |
+
return self::canonicalize($path . '/..');
|
375 |
+
}
|
376 |
+
|
377 |
+
/**
|
378 |
+
* Get base name of a path (the last component of a path - ie the filename).
|
379 |
+
*
|
380 |
+
* This function operates natively on the string and is not locale aware.
|
381 |
+
* It only works with "/" path separators.
|
382 |
+
*
|
383 |
+
* @return string the last component of a path
|
384 |
+
*/
|
385 |
+
public static function basename($path) {
|
386 |
+
$parts = explode('/', $path);
|
387 |
+
return array_pop($parts);
|
388 |
+
}
|
389 |
+
|
390 |
/**
|
391 |
* Returns absolute path from a relative path and root
|
392 |
* The result is canonicalized (dots and double-dots are resolved)
|
lib/classes/Paths.php
CHANGED
@@ -8,6 +8,225 @@ use \WebPExpress\PathHelper;
|
|
8 |
|
9 |
class Paths
|
10 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
public static function createDirIfMissing($dir)
|
13 |
{
|
@@ -28,44 +247,33 @@ class Paths
|
|
28 |
return (substr($rel, 0, 3) != '../');
|
29 |
}
|
30 |
|
31 |
-
/**
|
32 |
-
* Return relative dir - relative to realpath(document root)
|
33 |
-
*/
|
34 |
-
public static function getRelDir($dir)
|
35 |
-
{
|
36 |
-
return PathHelper::getRelDir(realpath($_SERVER['DOCUMENT_ROOT']), $dir);
|
37 |
-
}
|
38 |
-
|
39 |
-
|
40 |
/**
|
41 |
* Return absolute dir.
|
42 |
-
*
|
|
|
43 |
* - trailing dash is removed - we don't use that around here.
|
44 |
*
|
45 |
-
*
|
46 |
-
*
|
47 |
-
* (this takes care of resolving '../' and './', but does NOT resolve soft links)
|
48 |
*/
|
49 |
public static function getAbsDir($dir)
|
50 |
{
|
|
|
|
|
|
|
51 |
$result = realpath($dir);
|
52 |
if ($result === false) {
|
53 |
$dir = PathHelper::canonicalize($dir);
|
54 |
} else {
|
55 |
$dir = $result;
|
56 |
-
}
|
57 |
-
return rtrim($dir, '/');
|
58 |
-
}
|
59 |
-
|
60 |
-
// ------------ Document Root -------------
|
61 |
|
62 |
-
public static function getDocumentRootAbs()
|
63 |
-
{
|
64 |
-
return self::getAbsDir($_SERVER["DOCUMENT_ROOT"]);
|
65 |
}
|
66 |
|
67 |
// ------------ Home Dir -------------
|
68 |
|
|
|
|
|
69 |
public static function getHomeDirAbs()
|
70 |
{
|
71 |
if (!function_exists('get_home_path')) {
|
@@ -74,24 +282,24 @@ class Paths
|
|
74 |
return self::getAbsDir(get_home_path());
|
75 |
}
|
76 |
|
77 |
-
|
78 |
-
|
79 |
-
return self::getRelDir(self::getHomeDirAbs());
|
80 |
-
}
|
81 |
-
|
82 |
-
// ------------ Index Dir -------------
|
83 |
-
// (The Wordpress installation dir)
|
84 |
|
85 |
public static function getIndexDirAbs()
|
86 |
{
|
87 |
-
return self::getAbsDir(ABSPATH)
|
88 |
-
|
|
|
|
|
|
|
|
|
89 |
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
|
|
|
95 |
|
96 |
// ------------ .htaccess dir -------------
|
97 |
// (directory containing the relevant .htaccess)
|
@@ -103,6 +311,10 @@ class Paths
|
|
103 |
return FileHelper::canEditOrCreateFileHere($dirName . '/.htaccess');
|
104 |
}
|
105 |
|
|
|
|
|
|
|
|
|
106 |
public static function returnFirstWritableHTAccessDir($dirs)
|
107 |
{
|
108 |
foreach ($dirs as $dir) {
|
@@ -121,9 +333,14 @@ class Paths
|
|
121 |
}
|
122 |
public static function getContentDirRel()
|
123 |
{
|
124 |
-
return
|
|
|
|
|
|
|
|
|
125 |
}
|
126 |
|
|
|
127 |
public static function isWPContentDirMoved()
|
128 |
{
|
129 |
return (self::getContentDirAbs() != (ABSPATH . 'wp-content'));
|
@@ -134,6 +351,12 @@ class Paths
|
|
134 |
return !(self::isDirInsideDir(self::getContentDirAbs(), ABSPATH));
|
135 |
}
|
136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
|
138 |
// ------------ WebPExpress Content Dir -------------
|
139 |
// (the "webp-express" directory inside wp-content)
|
@@ -145,7 +368,7 @@ class Paths
|
|
145 |
|
146 |
public static function getWebPExpressContentDirRel()
|
147 |
{
|
148 |
-
return
|
149 |
}
|
150 |
|
151 |
public static function createContentDirIfMissing()
|
@@ -161,7 +384,7 @@ class Paths
|
|
161 |
}
|
162 |
public static function getUploadDirRel()
|
163 |
{
|
164 |
-
return
|
165 |
}
|
166 |
|
167 |
/*
|
@@ -193,7 +416,7 @@ class Paths
|
|
193 |
|
194 |
public static function getConfigDirRel()
|
195 |
{
|
196 |
-
return
|
197 |
}
|
198 |
|
199 |
public static function createConfigDirIfMissing()
|
@@ -237,9 +460,25 @@ APACHE
|
|
237 |
return self::getWebPExpressContentDirAbs() . '/webp-images';
|
238 |
}
|
239 |
|
240 |
-
public static function
|
|
|
|
|
|
|
|
|
|
|
241 |
{
|
242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
}
|
244 |
|
245 |
public static function createCacheDirIfMissing()
|
@@ -247,7 +486,7 @@ APACHE
|
|
247 |
return self::createDirIfMissing(self::getCacheDirAbs());
|
248 |
}
|
249 |
|
250 |
-
// ------------
|
251 |
|
252 |
public static function getLogDirAbs()
|
253 |
{
|
@@ -261,10 +500,6 @@ APACHE
|
|
261 |
return self::getAbsDir(WP_PLUGIN_DIR);
|
262 |
}
|
263 |
|
264 |
-
public static function getPluginDirRel()
|
265 |
-
{
|
266 |
-
return self::getRelDir(self::getPluginDirAbs());
|
267 |
-
}
|
268 |
|
269 |
public static function isPluginDirMovedOutOfAbsPath()
|
270 |
{
|
@@ -283,39 +518,6 @@ APACHE
|
|
283 |
return self::getAbsDir(WEBPEXPRESS_PLUGIN_DIR);
|
284 |
}
|
285 |
|
286 |
-
public static function getAbsDirId($absDir) {
|
287 |
-
switch ($absDir) {
|
288 |
-
case self::getContentDirAbs():
|
289 |
-
return 'wp-content';
|
290 |
-
case self::getIndexDirAbs():
|
291 |
-
return 'index';
|
292 |
-
case self::getHomeDirAbs():
|
293 |
-
return 'home';
|
294 |
-
case self::getPluginDirAbs():
|
295 |
-
return 'plugins';
|
296 |
-
case self::getUploadDirAbs():
|
297 |
-
return 'uploads';
|
298 |
-
}
|
299 |
-
return false;
|
300 |
-
}
|
301 |
-
|
302 |
-
public static function getAbsDirById($dirId) {
|
303 |
-
switch ($dirId) {
|
304 |
-
case 'wp-content':
|
305 |
-
return self::getContentDirAbs();
|
306 |
-
case 'index':
|
307 |
-
return self::getIndexDirAbs();
|
308 |
-
case 'home':
|
309 |
-
return self::getHomeDirAbs();
|
310 |
-
case 'plugins':
|
311 |
-
return self::getPluginDirAbs();
|
312 |
-
case 'uploads':
|
313 |
-
return self::getUploadDirAbs();
|
314 |
-
}
|
315 |
-
return false;
|
316 |
-
}
|
317 |
-
|
318 |
-
|
319 |
// ------------------------------------
|
320 |
// --------- Url paths ----------
|
321 |
// ------------------------------------
|
@@ -342,11 +544,77 @@ APACHE
|
|
342 |
return ltrim($path, '/\\');
|
343 |
}
|
344 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
345 |
// Get complete home url (no trailing slash). Ie: "http://example.com/blog"
|
346 |
public static function getHomeUrl()
|
347 |
{
|
348 |
-
if (!function_exists('
|
349 |
// silence is golden?
|
|
|
350 |
}
|
351 |
return untrailingslashit(home_url());
|
352 |
}
|
@@ -381,8 +649,10 @@ APACHE
|
|
381 |
return self::getUrlPathFromUrl(self::getContentUrl());
|
382 |
}
|
383 |
|
384 |
-
|
385 |
-
|
|
|
|
|
386 |
|
387 |
/**
|
388 |
* Get Url to WebP Express plugin (this is in fact an incomplete URL, you need to append ie '/webp-on-demand.php' to get a full URL)
|
@@ -415,17 +685,6 @@ APACHE
|
|
415 |
return self::getHomeUrl() . '/webp-express-web-service';
|
416 |
}
|
417 |
|
418 |
-
/**
|
419 |
-
* Calculate path to existing image, excluding
|
420 |
-
* (relative to document root)
|
421 |
-
* Ie: "/webp-express-test/wordpress/wp-content/webp-express/webp-images/webp-express-test/wordpress/"
|
422 |
-
* This is needed for the .htaccess
|
423 |
-
*/
|
424 |
-
public static function getPathToExisting()
|
425 |
-
{
|
426 |
-
return self::getCacheDirRel() . '/doc-root/' . self::getHomeDirRel();
|
427 |
-
}
|
428 |
-
|
429 |
public static function getUrlsAndPathsForTheJavascript()
|
430 |
{
|
431 |
return [
|
@@ -436,9 +695,6 @@ APACHE
|
|
436 |
'filePaths' => [
|
437 |
'webpExpressRoot' => self::getWebPExpressPluginDirAbs(),
|
438 |
'destinationRoot' => self::getCacheDirAbs(),
|
439 |
-
'configRelToDocRoot' => self::getConfigDirRel(),
|
440 |
-
'pluginRelToDocRoot' => self::getPluginDirRel(),
|
441 |
-
|
442 |
]
|
443 |
];
|
444 |
}
|
8 |
|
9 |
class Paths
|
10 |
{
|
11 |
+
public static function areAllImageRootsWithinDocRoot() {
|
12 |
+
if (!PathHelper::isDocRootAvailable()) {
|
13 |
+
return false;
|
14 |
+
}
|
15 |
+
|
16 |
+
$roots = self::getImageRootIds();
|
17 |
+
foreach ($roots as $dirId) {
|
18 |
+
$dir = self::getAbsDirById($dirId);
|
19 |
+
if (!PathHelper::canCalculateRelPathFromDocRootToDir($dir)) {
|
20 |
+
return false;
|
21 |
+
}
|
22 |
+
}
|
23 |
+
return true;
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Check if we can use document root for calculating relative paths (which may not contain "/.." directory traversal)
|
28 |
+
*
|
29 |
+
* Note that this method allows document root to be outside open_basedir as long as document root is
|
30 |
+
* non-empty AND it is possible to calculate relative paths to all image roots (including "index").
|
31 |
+
* Here is a case when a relative CAN be calculated:
|
32 |
+
* - Document root is configured to "/var/www/website" - which is also the absolute file path.
|
33 |
+
* - open_basedir is set to "/var/www/website/wordpress"
|
34 |
+
* - uploads is in "/var/www/website/wordpress/wp-content/uploads" (within open_basedir, as it should)
|
35 |
+
* - "/wp-uploads" symlinks to "/var/www/website/wordpress")
|
36 |
+
* - Wordpress has been configured to use "/wp-uploads" path for uploads.
|
37 |
+
*
|
38 |
+
* What happens?
|
39 |
+
* First, it is tested if the configured upload path ("/wp-uploads") begins with the configured document root ("/var/www/website").
|
40 |
+
* This fails.
|
41 |
+
* Next, it is tested if the uploads path can be resolved. It can, as it is within the open_basedir.
|
42 |
+
* Next, it is tested if the *resolved* the uploads path begins with the configured document root.
|
43 |
+
* As "/var/www/website/wordpress/wp-content/uploads" begins with "/var/www/website", we have a match.
|
44 |
+
* The relative path can be calculated to be "wordpress/wp-content/uploads".
|
45 |
+
* Later, when the relative path is used, it will be used as $docRoot + "/" + $relPath, which
|
46 |
+
* will be "/var/www/website/wordpress/wp-content/uploads". All is well.
|
47 |
+
*
|
48 |
+
* Here is a case where it CAN NOT be calculated:
|
49 |
+
* - Document root is configured to "/the-website", which symlinks to "/var/www/website"
|
50 |
+
* - open_basedir is set to "/var/www/website/wordpress"
|
51 |
+
* - uploads is in "/var/www/website/wordpress/wp-content/uploads" and wordpress is configured to use that upload path.
|
52 |
+
*
|
53 |
+
* What happens?
|
54 |
+
* First, it is tested if the configured upload path begins with the configured document root
|
55 |
+
* "/var/www/website/wordpress/wp-content/uploads" does not begin with "/the-website", so it fails.
|
56 |
+
* Next, it is tested if the *resolved* the uploads path begins with the configured document root.
|
57 |
+
* The resolved uploads path is the same as the configured so it also fails.
|
58 |
+
* Next, it is tested if Document root can be resolved. It can not, as the resolved path is not within open_basedir.
|
59 |
+
* If it could, it would have been tested if the resolved path begins with the resolved document root and we would have
|
60 |
+
* gotten a yes, and the relative path would have been "wordpress/wp-content/uploads" and it would work.
|
61 |
+
* However: Document root could not be resolved and we could not get a result.
|
62 |
+
* To sum the scenario up:
|
63 |
+
* If document root is configured to a symlink which cannot be resolved then it will only be possible to get relative paths
|
64 |
+
* when all other configured paths begins are relative to that symlink.
|
65 |
+
*/
|
66 |
+
public static function canUseDocRootForRelPaths() {
|
67 |
+
if (!PathHelper::isDocRootAvailable()) {
|
68 |
+
return false;
|
69 |
+
}
|
70 |
+
return self::areAllImageRootsWithinDocRoot();
|
71 |
+
}
|
72 |
+
|
73 |
+
public static function canCalculateRelPathFromDocRootToDir($absPath) {
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Check if we can use document root for structuring the cache dir.
|
78 |
+
*
|
79 |
+
* In order to structure the images by doc-root, WebP Express needs all images to be within document root.
|
80 |
+
* Does WebP Express in addition to this need to be able to resolve document root?
|
81 |
+
* Short answer is yes.
|
82 |
+
* The long answer is available as a comment inside ConvertHelperIndependent::getDestination()
|
83 |
+
*
|
84 |
+
*/
|
85 |
+
public static function canUseDocRootForStructuringCacheDir() {
|
86 |
+
return (PathHelper::isDocRootAvailableAndResolvable() && self::canUseDocRootForRelPaths());
|
87 |
+
}
|
88 |
+
|
89 |
+
public static function docRootStatusText()
|
90 |
+
{
|
91 |
+
if (!PathHelper::isDocRootAvailable()) {
|
92 |
+
if (!isset($_SERVER['DOCUMENT_ROOT'])) {
|
93 |
+
return 'Unavailable (DOCUMENT_ROOT is not set in the global $_SERVER var)';
|
94 |
+
}
|
95 |
+
if ($_SERVER['DOCUMENT_ROOT'] == '') {
|
96 |
+
return 'Unavailable (empty string)';
|
97 |
+
}
|
98 |
+
return 'Unavailable';
|
99 |
+
}
|
100 |
+
|
101 |
+
$imageRootsWithin = self::canUseDocRootForRelPaths();
|
102 |
+
if (!PathHelper::isDocRootAvailableAndResolvable()) {
|
103 |
+
$status = 'Available, but either non-existing or not within open_basedir.' .
|
104 |
+
($imageRootsWithin ? '' : ' And not all image roots are within that document root.');
|
105 |
+
} elseif (!$imageRootsWithin) {
|
106 |
+
$status = 'Available, but not all image roots are within that document root.';
|
107 |
+
} else {
|
108 |
+
$status = 'Available and its "realpath" is available too.';
|
109 |
+
}
|
110 |
+
if (self::canUseDocRootForStructuringCacheDir()) {
|
111 |
+
$status .= ' Can be used for structuring cache dir.';
|
112 |
+
} else {
|
113 |
+
$status .= ' Cannot be used for structuring cache dir.';
|
114 |
+
}
|
115 |
+
return $status;
|
116 |
+
}
|
117 |
+
|
118 |
+
public static function getAbsDirId($absDir) {
|
119 |
+
switch ($absDir) {
|
120 |
+
case self::getContentDirAbs():
|
121 |
+
return 'wp-content';
|
122 |
+
case self::getIndexDirAbs():
|
123 |
+
return 'index';
|
124 |
+
case self::getHomeDirAbs():
|
125 |
+
return 'home';
|
126 |
+
case self::getPluginDirAbs():
|
127 |
+
return 'plugins';
|
128 |
+
case self::getUploadDirAbs():
|
129 |
+
return 'uploads';
|
130 |
+
case self::getThemesDirAbs():
|
131 |
+
return 'themes';
|
132 |
+
case self::getCacheDirAbs():
|
133 |
+
return 'cache';
|
134 |
+
}
|
135 |
+
return false;
|
136 |
+
}
|
137 |
+
|
138 |
+
public static function getAbsDirById($dirId) {
|
139 |
+
switch ($dirId) {
|
140 |
+
case 'wp-content':
|
141 |
+
return self::getContentDirAbs();
|
142 |
+
case 'index':
|
143 |
+
return self::getIndexDirAbs();
|
144 |
+
case 'home':
|
145 |
+
// "home" is still needed (used in PluginDeactivate.php)
|
146 |
+
return self::getHomeDirAbs();
|
147 |
+
case 'plugins':
|
148 |
+
return self::getPluginDirAbs();
|
149 |
+
case 'uploads':
|
150 |
+
return self::getUploadDirAbs();
|
151 |
+
case 'themes':
|
152 |
+
return self::getThemesDirAbs();
|
153 |
+
case 'cache':
|
154 |
+
return self::getCacheDirAbs();
|
155 |
+
}
|
156 |
+
return false;
|
157 |
+
}
|
158 |
+
|
159 |
+
/**
|
160 |
+
* Get ids for folders where SOURCE images may reside
|
161 |
+
*/
|
162 |
+
public static function getImageRootIds() {
|
163 |
+
return ['uploads', 'themes', 'plugins', 'wp-content', 'index'];
|
164 |
+
}
|
165 |
+
|
166 |
+
public static function findImageRootOfPath($path, $rootIdsToSearch) {
|
167 |
+
foreach ($rootIdsToSearch as $rootId) {
|
168 |
+
if (PathHelper::isPathWithinExistingDirPath($path, self::getAbsDirById($rootId))) {
|
169 |
+
return $rootId;
|
170 |
+
}
|
171 |
+
}
|
172 |
+
return false;
|
173 |
+
}
|
174 |
+
|
175 |
+
public static function getImageRootsDefForSelectedIds($ids) {
|
176 |
+
$canUseDocRootForRelPaths = self::canUseDocRootForRelPaths();
|
177 |
+
|
178 |
+
$mapping = [];
|
179 |
+
foreach ($ids as $rootId) {
|
180 |
+
$obj = [
|
181 |
+
'id' => $rootId,
|
182 |
+
];
|
183 |
+
$absPath = self::getAbsDirById($rootId);
|
184 |
+
if ($canUseDocRootForRelPaths) {
|
185 |
+
$obj['rel-path'] = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($absPath);
|
186 |
+
} else {
|
187 |
+
$obj['abs-path'] = $absPath;
|
188 |
+
}
|
189 |
+
$obj['url'] = self::getUrlById($rootId);
|
190 |
+
$mapping[] = $obj;
|
191 |
+
}
|
192 |
+
return $mapping;
|
193 |
+
}
|
194 |
+
|
195 |
+
public static function getImageRootsDef()
|
196 |
+
{
|
197 |
+
return self::getImageRootsDefForSelectedIds(self::getImageRootIds());
|
198 |
+
}
|
199 |
+
|
200 |
+
public static function filterOutSubRoots($rootIds)
|
201 |
+
{
|
202 |
+
// Get dirs of enabled roots
|
203 |
+
$dirs = [];
|
204 |
+
foreach ($rootIds as $rootId) {
|
205 |
+
$dirs[] = self::getAbsDirById($rootId);
|
206 |
+
}
|
207 |
+
|
208 |
+
// Filter out dirs which are below other dirs
|
209 |
+
$dirsToSkip = [];
|
210 |
+
foreach ($dirs as $dirToExamine) {
|
211 |
+
foreach ($dirs as $dirToCompareAgainst) {
|
212 |
+
if ($dirToExamine == $dirToCompareAgainst) {
|
213 |
+
continue;
|
214 |
+
}
|
215 |
+
if (self::isDirInsideDir($dirToExamine, $dirToCompareAgainst)) {
|
216 |
+
$dirsToSkip[] = $dirToExamine;
|
217 |
+
break;
|
218 |
+
}
|
219 |
+
}
|
220 |
+
}
|
221 |
+
$dirs = array_diff($dirs, $dirsToSkip);
|
222 |
+
|
223 |
+
// back to ids
|
224 |
+
$result = [];
|
225 |
+
foreach ($dirs as $dir) {
|
226 |
+
$result[] = self::getAbsDirId($dir);
|
227 |
+
}
|
228 |
+
return $result;
|
229 |
+
}
|
230 |
|
231 |
public static function createDirIfMissing($dir)
|
232 |
{
|
247 |
return (substr($rel, 0, 3) != '../');
|
248 |
}
|
249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
/**
|
251 |
* Return absolute dir.
|
252 |
+
*
|
253 |
+
* - Path is canonicalized (without resolving symlinks)
|
254 |
* - trailing dash is removed - we don't use that around here.
|
255 |
*
|
256 |
+
* We do not resolve symlinks anymore. Information was lost that way.
|
257 |
+
* And in some cases we needed the unresolved path - for example in the .htaccess.
|
|
|
258 |
*/
|
259 |
public static function getAbsDir($dir)
|
260 |
{
|
261 |
+
$dir = PathHelper::canonicalize($dir);
|
262 |
+
return rtrim($dir, '/');
|
263 |
+
/*
|
264 |
$result = realpath($dir);
|
265 |
if ($result === false) {
|
266 |
$dir = PathHelper::canonicalize($dir);
|
267 |
} else {
|
268 |
$dir = $result;
|
269 |
+
}*/
|
|
|
|
|
|
|
|
|
270 |
|
|
|
|
|
|
|
271 |
}
|
272 |
|
273 |
// ------------ Home Dir -------------
|
274 |
|
275 |
+
// PS: Home dir is not the same as index dir.
|
276 |
+
// For example, if Wordpress folder has been moved (method 2), the home dir could be below.
|
277 |
public static function getHomeDirAbs()
|
278 |
{
|
279 |
if (!function_exists('get_home_path')) {
|
282 |
return self::getAbsDir(get_home_path());
|
283 |
}
|
284 |
|
285 |
+
// ------------ Index Dir (WP root dir) -------------
|
286 |
+
// (The Wordpress installation dir- where index.php and wp-load.php resides)
|
|
|
|
|
|
|
|
|
|
|
287 |
|
288 |
public static function getIndexDirAbs()
|
289 |
{
|
290 |
+
// We used to return self::getAbsDir(ABSPATH), which used realpath.
|
291 |
+
// It has been changed now, as it seems we do not need realpath for ABSPATH, as it is defined
|
292 |
+
// (in wp-load.php) as dirname(__FILE__) . "/" and according to this link, __FILE__ returns resolved paths:
|
293 |
+
// https://stackoverflow.com/questions/3221771/how-do-you-get-php-symlinks-and-file-to-work-together-nicely
|
294 |
+
// AND a user reported an open_basedir restriction problem thrown by realpath($_SERVER['DOCUMENT_ROOT']),
|
295 |
+
// due to symlinking and opendir restriction (see #322)
|
296 |
|
297 |
+
return rtrim(ABSPATH, '/');
|
298 |
+
|
299 |
+
// TODO: read up on this, regarding realpath:
|
300 |
+
// https://github.com/twigphp/Twig/issues/2707
|
301 |
|
302 |
+
}
|
303 |
|
304 |
// ------------ .htaccess dir -------------
|
305 |
// (directory containing the relevant .htaccess)
|
311 |
return FileHelper::canEditOrCreateFileHere($dirName . '/.htaccess');
|
312 |
}
|
313 |
|
314 |
+
public static function canWriteHTAccessRulesInDir($dirId) {
|
315 |
+
return self::canWriteHTAccessRulesHere(self::getAbsDirById($dirId));
|
316 |
+
}
|
317 |
+
|
318 |
public static function returnFirstWritableHTAccessDir($dirs)
|
319 |
{
|
320 |
foreach ($dirs as $dir) {
|
333 |
}
|
334 |
public static function getContentDirRel()
|
335 |
{
|
336 |
+
return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getContentDirAbs());
|
337 |
+
}
|
338 |
+
public static function getContentDirRelToPluginDir()
|
339 |
+
{
|
340 |
+
return PathHelper::getRelDir(self::getPluginDirAbs(), self::getContentDirAbs());
|
341 |
}
|
342 |
|
343 |
+
|
344 |
public static function isWPContentDirMoved()
|
345 |
{
|
346 |
return (self::getContentDirAbs() != (ABSPATH . 'wp-content'));
|
351 |
return !(self::isDirInsideDir(self::getContentDirAbs(), ABSPATH));
|
352 |
}
|
353 |
|
354 |
+
// ------------ Themes Dir -------------
|
355 |
+
|
356 |
+
public static function getThemesDirAbs()
|
357 |
+
{
|
358 |
+
return self::getContentDirAbs() . '/themes';
|
359 |
+
}
|
360 |
|
361 |
// ------------ WebPExpress Content Dir -------------
|
362 |
// (the "webp-express" directory inside wp-content)
|
368 |
|
369 |
public static function getWebPExpressContentDirRel()
|
370 |
{
|
371 |
+
return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getWebPExpressContentDirAbs());
|
372 |
}
|
373 |
|
374 |
public static function createContentDirIfMissing()
|
384 |
}
|
385 |
public static function getUploadDirRel()
|
386 |
{
|
387 |
+
return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getUploadDirAbs());
|
388 |
}
|
389 |
|
390 |
/*
|
416 |
|
417 |
public static function getConfigDirRel()
|
418 |
{
|
419 |
+
return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getConfigDirAbs());
|
420 |
}
|
421 |
|
422 |
public static function createConfigDirIfMissing()
|
460 |
return self::getWebPExpressContentDirAbs() . '/webp-images';
|
461 |
}
|
462 |
|
463 |
+
public static function getCacheDirRelToDocRoot()
|
464 |
+
{
|
465 |
+
return PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(self::getCacheDirAbs());
|
466 |
+
}
|
467 |
+
|
468 |
+
public static function getCacheDirForImageRoot($destinationFolder, $destinationStructure, $imageRootId)
|
469 |
{
|
470 |
+
if (($destinationFolder == 'mingled') && ($imageRootId == 'uploads')) {
|
471 |
+
return self::getUploadDirAbs();
|
472 |
+
}
|
473 |
+
|
474 |
+
if ($destinationStructure == 'doc-root') {
|
475 |
+
$relPath = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(
|
476 |
+
self::getAbsDirById($imageRootId)
|
477 |
+
);
|
478 |
+
return self::getCacheDirAbs() . '/doc-root/' . $relPath;
|
479 |
+
} else {
|
480 |
+
return self::getCacheDirAbs() . '/' . $imageRootId;
|
481 |
+
}
|
482 |
}
|
483 |
|
484 |
public static function createCacheDirIfMissing()
|
486 |
return self::createDirIfMissing(self::getCacheDirAbs());
|
487 |
}
|
488 |
|
489 |
+
// ------------ Log Dir -------------
|
490 |
|
491 |
public static function getLogDirAbs()
|
492 |
{
|
500 |
return self::getAbsDir(WP_PLUGIN_DIR);
|
501 |
}
|
502 |
|
|
|
|
|
|
|
|
|
503 |
|
504 |
public static function isPluginDirMovedOutOfAbsPath()
|
505 |
{
|
518 |
return self::getAbsDir(WEBPEXPRESS_PLUGIN_DIR);
|
519 |
}
|
520 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
// ------------------------------------
|
522 |
// --------- Url paths ----------
|
523 |
// ------------------------------------
|
544 |
return ltrim($path, '/\\');
|
545 |
}
|
546 |
|
547 |
+
public static function getUrlById($dirId) {
|
548 |
+
switch ($dirId) {
|
549 |
+
case 'wp-content':
|
550 |
+
return self::getContentUrl();
|
551 |
+
case 'index':
|
552 |
+
return self::getHomeUrl();
|
553 |
+
case 'home':
|
554 |
+
return self::getHomeUrl();
|
555 |
+
case 'plugins':
|
556 |
+
return self::getPluginUrl();
|
557 |
+
case 'uploads':
|
558 |
+
return self::getUploadUrl();
|
559 |
+
case 'themes':
|
560 |
+
return self::getThemesUrl();
|
561 |
+
}
|
562 |
+
return false;
|
563 |
+
}
|
564 |
+
|
565 |
+
/**
|
566 |
+
* Get destination root url and path, provided rootId and some configuration options
|
567 |
+
*
|
568 |
+
* This method kind of establishes the overall structure of the cache dir.
|
569 |
+
* (but not quite, as the logic is also in ConverterHelper::getDestination).
|
570 |
+
*
|
571 |
+
* @param string $rootId
|
572 |
+
* @param string $destinationFolder ("mingled" or "separate")
|
573 |
+
* @param string $destinationStructure ("doc-root" or "image-roots")
|
574 |
+
*
|
575 |
+
* @return array url and abs-path of destination root
|
576 |
+
*/
|
577 |
+
public static function destinationRoot($rootId, $destinationFolder, $destinationStructure)
|
578 |
+
{
|
579 |
+
if (($destinationFolder == 'mingled') && ($rootId == 'uploads')) {
|
580 |
+
return [
|
581 |
+
'url' => self::getUrlById('uploads'),
|
582 |
+
'abs-path' => self::getUploadDirAbs()
|
583 |
+
];
|
584 |
+
} else {
|
585 |
+
|
586 |
+
// Its within these bases:
|
587 |
+
$destUrl = self::getUrlById('wp-content') . '/webp-express/webp-images';
|
588 |
+
$destPath = self::getAbsDirById('wp-content') . '/webp-express/webp-images';
|
589 |
+
|
590 |
+
if (($destinationStructure == 'doc-root') && self::canUseDocRootForStructuringCacheDir()) {
|
591 |
+
$relPathFromDocRootToSourceImageRoot = PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed(
|
592 |
+
self::getAbsDirById($rootId)
|
593 |
+
);
|
594 |
+
return [
|
595 |
+
'url' => $destUrl . '/doc-root/' . $relPathFromDocRootToSourceImageRoot,
|
596 |
+
'abs-path' => $destPath . '/doc-root/' . $relPathFromDocRootToSourceImageRoot
|
597 |
+
];
|
598 |
+
} else {
|
599 |
+
return [
|
600 |
+
'url' => $destUrl . '/' . $rootId,
|
601 |
+
'abs-path' => $destPath . '/' . $rootId
|
602 |
+
];
|
603 |
+
}
|
604 |
+
}
|
605 |
+
}
|
606 |
+
|
607 |
+
public static function getUrlPathById($dirId) {
|
608 |
+
return self::getUrlPathFromUrl(self::getUrlById($dirId));
|
609 |
+
}
|
610 |
+
|
611 |
+
|
612 |
// Get complete home url (no trailing slash). Ie: "http://example.com/blog"
|
613 |
public static function getHomeUrl()
|
614 |
{
|
615 |
+
if (!function_exists('home_url')) {
|
616 |
// silence is golden?
|
617 |
+
// bad joke. Need to handle this...
|
618 |
}
|
619 |
return untrailingslashit(home_url());
|
620 |
}
|
649 |
return self::getUrlPathFromUrl(self::getContentUrl());
|
650 |
}
|
651 |
|
652 |
+
public static function getThemesUrl()
|
653 |
+
{
|
654 |
+
return self::getContentUrl() . '/themes';
|
655 |
+
}
|
656 |
|
657 |
/**
|
658 |
* Get Url to WebP Express plugin (this is in fact an incomplete URL, you need to append ie '/webp-on-demand.php' to get a full URL)
|
685 |
return self::getHomeUrl() . '/webp-express-web-service';
|
686 |
}
|
687 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
688 |
public static function getUrlsAndPathsForTheJavascript()
|
689 |
{
|
690 |
return [
|
695 |
'filePaths' => [
|
696 |
'webpExpressRoot' => self::getWebPExpressPluginDirAbs(),
|
697 |
'destinationRoot' => self::getCacheDirAbs(),
|
|
|
|
|
|
|
698 |
]
|
699 |
];
|
700 |
}
|
lib/classes/PlatformInfo.php
CHANGED
@@ -23,39 +23,88 @@ class PlatformInfo
|
|
23 |
return ( strpos( $server, 'litespeed') !== false );
|
24 |
}
|
25 |
|
|
|
|
|
|
|
|
|
|
|
26 |
public static function isApacheOrLiteSpeed()
|
27 |
{
|
28 |
return self::isApache() || self::isLiteSpeed();
|
29 |
}
|
30 |
|
31 |
/**
|
32 |
-
*
|
33 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
*/
|
35 |
-
public static function
|
36 |
{
|
37 |
-
if (function_exists(
|
38 |
-
$
|
39 |
-
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
}
|
42 |
}
|
43 |
-
// TODO: Perhaps also try looking at phpinfo, like Wordpress does in apache_mod_loaded
|
44 |
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
}
|
47 |
|
|
|
|
|
|
|
|
|
48 |
public static function definitelyGotApacheModule($mod)
|
49 |
{
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
}
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
}
|
58 |
|
|
|
59 |
public static function definitelyNotGotModRewrite()
|
60 |
{
|
61 |
return self::definitelyNotGotApacheModule('mod_rewrite');
|
23 |
return ( strpos( $server, 'litespeed') !== false );
|
24 |
}
|
25 |
|
26 |
+
public static function isNginx()
|
27 |
+
{
|
28 |
+
return (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false);
|
29 |
+
}
|
30 |
+
|
31 |
public static function isApacheOrLiteSpeed()
|
32 |
{
|
33 |
return self::isApache() || self::isLiteSpeed();
|
34 |
}
|
35 |
|
36 |
/**
|
37 |
+
* Check if an Apache module is available.
|
38 |
+
*
|
39 |
+
* If apache_get_modules() exists, it is used. That function is however only available in mod_php installs.
|
40 |
+
* Otherwise the Wordpress function "apache_mod_loaded" is tried, which examines phpinfo() output.
|
41 |
+
* However, it seems there is no module output on php-fpm setups.
|
42 |
+
* So on php-fpm, we cannot come with an answer.
|
43 |
+
* https://stackoverflow.com/questions/9021425/how-to-check-if-mod-rewrite-is-enabled-in-php
|
44 |
+
*
|
45 |
+
* @param string $mod Name of module - ie "mod_rewrite"
|
46 |
+
* @return boolean|null Return if module is available, or null if indeterminate
|
47 |
*/
|
48 |
+
public static function gotApacheModule($mod)
|
49 |
{
|
50 |
+
if (function_exists('apache_get_modules')) {
|
51 |
+
return in_array($mod, apache_get_modules());
|
52 |
+
}
|
53 |
+
|
54 |
+
// Revert to Wordpress method, which examines output from phpinfo as well
|
55 |
+
if (function_exists('apache_mod_loaded')) {
|
56 |
+
$result = apache_mod_loaded($mod, null);
|
57 |
+
|
58 |
+
// If we got a real result, return it.
|
59 |
+
if ($result != null) {
|
60 |
+
return $result;
|
61 |
}
|
62 |
}
|
|
|
63 |
|
64 |
+
// We could run shell_exec("apachectl -l"), as suggested here:
|
65 |
+
// https://stackoverflow.com/questions/9021425/how-to-check-if-mod-rewrite-is-enabled-in-php
|
66 |
+
// But it does not seem to return all modules in my php-fpm setup.
|
67 |
+
|
68 |
+
// Currently we got no more tools.
|
69 |
+
return null;
|
70 |
+
|
71 |
}
|
72 |
|
73 |
+
/**
|
74 |
+
* It is not always possible to determine if apache has a given module...
|
75 |
+
* We shall not fool anyone into thinking otherwise by providing a "got" method like Wordpress does...
|
76 |
+
*/
|
77 |
public static function definitelyGotApacheModule($mod)
|
78 |
{
|
79 |
+
return (self::gotApacheModule($mod) === true);
|
80 |
+
}
|
81 |
+
|
82 |
+
public static function definitelyNotGotApacheModule($mod)
|
83 |
+
{
|
84 |
+
return (self::gotApacheModule($mod) === false);
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Check if mod_rewrite or IIS rewrite is available.
|
89 |
+
*
|
90 |
+
* @return boolean|null Return bool if it can be determined, or null if not
|
91 |
+
*/
|
92 |
+
public static function gotRewriteModule()
|
93 |
+
{
|
94 |
+
$gotModRewrite = self::gotApacheModule('mod_rewrite');
|
95 |
+
if (!is_null($gotModRewrite)) {
|
96 |
+
return $gotModRewrite;
|
97 |
}
|
98 |
+
|
99 |
+
// Got the IIS check here: https://stackoverflow.com/a/21249745/842756
|
100 |
+
// but have not tested it...
|
101 |
+
if (isset($_SERVER['IIS_UrlRewriteModule'])) {
|
102 |
+
return true;
|
103 |
+
}
|
104 |
+
return null;
|
105 |
}
|
106 |
|
107 |
+
|
108 |
public static function definitelyNotGotModRewrite()
|
109 |
{
|
110 |
return self::definitelyNotGotApacheModule('mod_rewrite');
|
lib/classes/PluginActivate.php
CHANGED
@@ -28,7 +28,8 @@ class PluginActivate
|
|
28 |
|
29 |
private static function reactivate()
|
30 |
{
|
31 |
-
$config = Config::
|
|
|
32 |
if ($config === false) {
|
33 |
Messenger::addMessage(
|
34 |
'error',
|
@@ -123,7 +124,7 @@ class PluginActivate
|
|
123 |
'<a href="' . Paths::getSettingsUrl() . '">configure it here</a>.'
|
124 |
);
|
125 |
|
126 |
-
// While not
|
127 |
CapabilityTest::copyCapabilityTestsToWpContent();
|
128 |
}
|
129 |
}
|
28 |
|
29 |
private static function reactivate()
|
30 |
{
|
31 |
+
$config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
|
32 |
+
|
33 |
if ($config === false) {
|
34 |
Messenger::addMessage(
|
35 |
'error',
|
124 |
'<a href="' . Paths::getSettingsUrl() . '">configure it here</a>.'
|
125 |
);
|
126 |
|
127 |
+
// While not necessary, lets get those tests copied right away. Some servers are a bit slow to pick up on changes in the filesystem
|
128 |
CapabilityTest::copyCapabilityTestsToWpContent();
|
129 |
}
|
130 |
}
|
lib/classes/PluginDeactivate.php
CHANGED
@@ -2,21 +2,24 @@
|
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
-
use \WebPExpress\HTAccess;
|
6 |
-
use \WebPExpress\Messenger;
|
7 |
-
|
8 |
class PluginDeactivate
|
9 |
{
|
10 |
// The hook was registred in AdminInit
|
11 |
public static function deactivate() {
|
12 |
|
13 |
-
$
|
14 |
-
|
|
|
15 |
// Oh no. We failed removing the rules
|
16 |
$msg = "<b>Sorry, can't let you disable WebP Express!</b><br>" .
|
17 |
'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>' .
|
18 |
'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.' .
|
19 |
-
'<br>It concerns the following files:<br>'
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
Messenger::addMessage(
|
22 |
'error',
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
|
|
|
|
|
|
5 |
class PluginDeactivate
|
6 |
{
|
7 |
// The hook was registred in AdminInit
|
8 |
public static function deactivate() {
|
9 |
|
10 |
+
list($success, $failures, $successes) = HTAccess::deactivateHTAccessRules();
|
11 |
+
|
12 |
+
if (!$success) {
|
13 |
// Oh no. We failed removing the rules
|
14 |
$msg = "<b>Sorry, can't let you disable WebP Express!</b><br>" .
|
15 |
'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>' .
|
16 |
'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.' .
|
17 |
+
'<br>It concerns the following files:<br>';
|
18 |
+
|
19 |
+
|
20 |
+
foreach ($failures as $rootId) {
|
21 |
+
$msg .= '- ' . Paths::getAbsDirById($rootId) . '/.htaccess<br>';
|
22 |
+
}
|
23 |
|
24 |
Messenger::addMessage(
|
25 |
'error',
|
lib/classes/SanityCheck.php
CHANGED
@@ -2,12 +2,22 @@
|
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
|
|
5 |
use \WebPExpress\Sanitize;
|
6 |
use \WebPExpress\SanityException;
|
7 |
|
8 |
class SanityCheck
|
9 |
{
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
/**
|
12 |
*
|
13 |
* @param string $input string to test for NUL char
|
@@ -15,7 +25,7 @@ class SanityCheck
|
|
15 |
public static function mustBeString($input, $errorMsg = 'String expected')
|
16 |
{
|
17 |
if (gettype($input) !== 'string') {
|
18 |
-
|
19 |
}
|
20 |
return $input;
|
21 |
}
|
@@ -30,7 +40,7 @@ class SanityCheck
|
|
30 |
{
|
31 |
self::mustBeString($input);
|
32 |
if (strpos($input, chr(0)) !== false) {
|
33 |
-
|
34 |
}
|
35 |
return $input;
|
36 |
}
|
@@ -43,12 +53,12 @@ class SanityCheck
|
|
43 |
*
|
44 |
* @param string $input string to test for control characters
|
45 |
*/
|
46 |
-
public static function noControlChars($input)
|
47 |
{
|
48 |
self::mustBeString($input);
|
49 |
self::noNUL($input);
|
50 |
if (preg_match('#[\x{0}-\x{1f}]#', $input)) {
|
51 |
-
|
52 |
}
|
53 |
return $input;
|
54 |
}
|
@@ -61,7 +71,7 @@ class SanityCheck
|
|
61 |
public static function notEmpty($input, $errorMsg = 'Must be non-empty')
|
62 |
{
|
63 |
if (empty($input)) {
|
64 |
-
|
65 |
}
|
66 |
return $input;
|
67 |
}
|
@@ -73,7 +83,7 @@ class SanityCheck
|
|
73 |
self::mustBeString($input);
|
74 |
self::noControlChars($input);
|
75 |
if (preg_match('#\.\.\/#', $input)) {
|
76 |
-
|
77 |
}
|
78 |
return $input;
|
79 |
}
|
@@ -86,17 +96,16 @@ class SanityCheck
|
|
86 |
// Prevent stream wrappers ("phar://", "php://" and the like)
|
87 |
// https://www.php.net/manual/en/wrappers.phar.php
|
88 |
if (preg_match('#^\\w+://#', Sanitize::removeNUL($input))) {
|
89 |
-
|
90 |
}
|
91 |
return $input;
|
92 |
}
|
93 |
|
94 |
-
public static function
|
95 |
{
|
96 |
self::notEmpty($input);
|
97 |
self::mustBeString($input);
|
98 |
self::noControlChars($input);
|
99 |
-
self::noDirectoryTraversal($input);
|
100 |
self::noStreamWrappers($input);
|
101 |
|
102 |
// PS: The following sanitize has no effect, as we have just tested that there are no NUL and
|
@@ -108,32 +117,42 @@ class SanityCheck
|
|
108 |
|
109 |
public static function pathWithoutDirectoryTraversal($input)
|
110 |
{
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
}
|
113 |
|
|
|
114 |
/**
|
115 |
* Beware: This does not take symlinks into account.
|
116 |
* I should make one that does. Until then, you should probably not call this method from outside this class
|
117 |
*/
|
118 |
-
|
119 |
{
|
120 |
self::path($input);
|
121 |
if (!(strpos($input, $beginsWith) === 0)) {
|
122 |
-
|
123 |
}
|
124 |
return $input;
|
125 |
}
|
126 |
|
127 |
-
|
128 |
-
$closestExistingFolder =
|
129 |
self::pathBeginsWith($closestExistingFolder, $beginsWith, $errorMsg);
|
130 |
}
|
131 |
|
132 |
-
|
133 |
{
|
134 |
// On microsoft we allow [drive letter]:\
|
135 |
if (!preg_match("#^[A-Z]:\\\\|/#", $input)) {
|
136 |
-
|
137 |
}
|
138 |
return $input;
|
139 |
}
|
@@ -169,34 +188,91 @@ class SanityCheck
|
|
169 |
if (self::isOnMicrosoft()) {
|
170 |
self::absPathMicrosoftStyle($input);
|
171 |
} else {
|
172 |
-
|
173 |
}
|
174 |
}
|
175 |
return $input;
|
176 |
}
|
177 |
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
}
|
193 |
-
$levelsUp++;
|
194 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
}
|
196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
/**
|
198 |
* Test that absolute path is in document root.
|
199 |
*
|
|
|
|
|
|
|
200 |
* It is acceptable if the absolute path does not exist
|
201 |
*/
|
202 |
public static function absPathIsInDocRoot($input, $errorMsg = 'Path is outside document root')
|
@@ -208,9 +284,10 @@ class SanityCheck
|
|
208 |
$docRoot = self::absPathExistsAndIsDir($docRoot);
|
209 |
|
210 |
// Use realpath to expand symbolic links and check if it exists
|
211 |
-
$docRootSymLinksExpanded = realpath($docRoot);
|
212 |
if ($docRootSymLinksExpanded === false) {
|
213 |
-
|
|
|
214 |
}
|
215 |
$docRootSymLinksExpanded = rtrim($docRootSymLinksExpanded, '/');
|
216 |
$docRootSymLinksExpanded = self::absPathExists($docRootSymLinksExpanded, 'Document root does not exist!');
|
@@ -226,11 +303,14 @@ class SanityCheck
|
|
226 |
return $input;
|
227 |
}
|
228 |
|
229 |
-
public static function absPathExists($input, $errorMsg = 'Path does not exist')
|
230 |
{
|
231 |
self::absPath($input);
|
232 |
if (@!file_exists($input)) {
|
233 |
-
|
|
|
|
|
|
|
234 |
}
|
235 |
return $input;
|
236 |
}
|
@@ -239,9 +319,9 @@ class SanityCheck
|
|
239 |
$input,
|
240 |
$errorMsg = 'Path points to a file (it should point to a directory)'
|
241 |
) {
|
242 |
-
self::absPathExists($input);
|
243 |
if (!is_dir($input)) {
|
244 |
-
|
245 |
}
|
246 |
return $input;
|
247 |
}
|
@@ -250,9 +330,9 @@ class SanityCheck
|
|
250 |
$input,
|
251 |
$errorMsg = 'Path points to a directory (it should not do that)'
|
252 |
) {
|
253 |
-
self::absPathExists($input, 'File does not exist');
|
254 |
if (@is_dir($input)) {
|
255 |
-
|
256 |
}
|
257 |
return $input;
|
258 |
}
|
@@ -278,7 +358,7 @@ class SanityCheck
|
|
278 |
self::noNUL($input);
|
279 |
self::mustBeString($input);
|
280 |
if (!preg_match($pattern, $input)) {
|
281 |
-
|
282 |
}
|
283 |
return $input;
|
284 |
}
|
@@ -289,7 +369,7 @@ class SanityCheck
|
|
289 |
self::mustBeString($input);
|
290 |
self::notEmpty($input);
|
291 |
if ((strpos($input, '[') !== 0) || (!is_array(json_decode($input)))) {
|
292 |
-
|
293 |
}
|
294 |
return $input;
|
295 |
}
|
@@ -300,7 +380,7 @@ class SanityCheck
|
|
300 |
self::mustBeString($input);
|
301 |
self::notEmpty($input);
|
302 |
if ((strpos($input, '{') !== 0) || (!is_object(json_decode($input)))) {
|
303 |
-
|
304 |
}
|
305 |
return $input;
|
306 |
}
|
2 |
|
3 |
namespace WebPExpress;
|
4 |
|
5 |
+
use \WebPExpress\PathHelper;
|
6 |
use \WebPExpress\Sanitize;
|
7 |
use \WebPExpress\SanityException;
|
8 |
|
9 |
class SanityCheck
|
10 |
{
|
11 |
|
12 |
+
private static function fail($errorMsg, $input)
|
13 |
+
{
|
14 |
+
// sanitize input before calling error_log(), it might be sent to file, mail, syslog etc.
|
15 |
+
error_log($errorMsg . '. input:' . Sanitize::removeNUL($input));
|
16 |
+
//error_log(get_magic_quotes_gpc() ? 'on' :'off');
|
17 |
+
throw new SanityException($errorMsg); // . '. Check debug.log for details (and make sure debugging is enabled)'
|
18 |
+
}
|
19 |
+
|
20 |
+
|
21 |
/**
|
22 |
*
|
23 |
* @param string $input string to test for NUL char
|
25 |
public static function mustBeString($input, $errorMsg = 'String expected')
|
26 |
{
|
27 |
if (gettype($input) !== 'string') {
|
28 |
+
self::fail($errorMsg, $input);
|
29 |
}
|
30 |
return $input;
|
31 |
}
|
40 |
{
|
41 |
self::mustBeString($input);
|
42 |
if (strpos($input, chr(0)) !== false) {
|
43 |
+
self::fail($errorMsg, $input);
|
44 |
}
|
45 |
return $input;
|
46 |
}
|
53 |
*
|
54 |
* @param string $input string to test for control characters
|
55 |
*/
|
56 |
+
public static function noControlChars($input, $errorMsg = 'Control characters are not allowed')
|
57 |
{
|
58 |
self::mustBeString($input);
|
59 |
self::noNUL($input);
|
60 |
if (preg_match('#[\x{0}-\x{1f}]#', $input)) {
|
61 |
+
self::fail($errorMsg, $input);
|
62 |
}
|
63 |
return $input;
|
64 |
}
|
71 |
public static function notEmpty($input, $errorMsg = 'Must be non-empty')
|
72 |
{
|
73 |
if (empty($input)) {
|
74 |
+
self::fail($errorMsg, '');
|
75 |
}
|
76 |
return $input;
|
77 |
}
|
83 |
self::mustBeString($input);
|
84 |
self::noControlChars($input);
|
85 |
if (preg_match('#\.\.\/#', $input)) {
|
86 |
+
self::fail($errorMsg, $input);
|
87 |
}
|
88 |
return $input;
|
89 |
}
|
96 |
// Prevent stream wrappers ("phar://", "php://" and the like)
|
97 |
// https://www.php.net/manual/en/wrappers.phar.php
|
98 |
if (preg_match('#^\\w+://#', Sanitize::removeNUL($input))) {
|
99 |
+
self::fail($errorMsg, $input);
|
100 |
}
|
101 |
return $input;
|
102 |
}
|
103 |
|
104 |
+
public static function pathDirectoryTraversalAllowed($input)
|
105 |
{
|
106 |
self::notEmpty($input);
|
107 |
self::mustBeString($input);
|
108 |
self::noControlChars($input);
|
|
|
109 |
self::noStreamWrappers($input);
|
110 |
|
111 |
// PS: The following sanitize has no effect, as we have just tested that there are no NUL and
|
117 |
|
118 |
public static function pathWithoutDirectoryTraversal($input)
|
119 |
{
|
120 |
+
self::pathDirectoryTraversalAllowed($input);
|
121 |
+
self::noDirectoryTraversal($input);
|
122 |
+
$input = Sanitize::path($input);
|
123 |
+
|
124 |
+
return $input;
|
125 |
+
}
|
126 |
+
|
127 |
+
public static function path($input)
|
128 |
+
{
|
129 |
+
return self::pathWithoutDirectoryTraversal($input);
|
130 |
}
|
131 |
|
132 |
+
|
133 |
/**
|
134 |
* Beware: This does not take symlinks into account.
|
135 |
* I should make one that does. Until then, you should probably not call this method from outside this class
|
136 |
*/
|
137 |
+
private static function pathBeginsWith($input, $beginsWith, $errorMsg = 'Path is outside allowed path')
|
138 |
{
|
139 |
self::path($input);
|
140 |
if (!(strpos($input, $beginsWith) === 0)) {
|
141 |
+
self::fail($errorMsg, $input);
|
142 |
}
|
143 |
return $input;
|
144 |
}
|
145 |
|
146 |
+
private static function pathBeginsWithSymLinksExpanded($input, $beginsWith, $errorMsg = 'Path is outside allowed path') {
|
147 |
+
$closestExistingFolder = PathHelper::findClosestExistingFolderSymLinksExpanded($input);
|
148 |
self::pathBeginsWith($closestExistingFolder, $beginsWith, $errorMsg);
|
149 |
}
|
150 |
|
151 |
+
private static function absPathMicrosoftStyle($input, $errorMsg = 'Not an fully qualified Windows path')
|
152 |
{
|
153 |
// On microsoft we allow [drive letter]:\
|
154 |
if (!preg_match("#^[A-Z]:\\\\|/#", $input)) {
|
155 |
+
self::fail($errorMsg, $input);
|
156 |
}
|
157 |
return $input;
|
158 |
}
|
188 |
if (self::isOnMicrosoft()) {
|
189 |
self::absPathMicrosoftStyle($input);
|
190 |
} else {
|
191 |
+
self::fail($errorMsg, $input);
|
192 |
}
|
193 |
}
|
194 |
return $input;
|
195 |
}
|
196 |
|
197 |
+
|
198 |
+
|
199 |
+
public static function absPathInOneOfTheseRoots()
|
200 |
+
{
|
201 |
+
|
202 |
+
}
|
203 |
+
|
204 |
+
|
205 |
+
/**
|
206 |
+
* Look if filepath is within a dir path.
|
207 |
+
* Also tries expanding symlinks
|
208 |
+
*
|
209 |
+
* @param string $filePath Path to file. It may be non-existing.
|
210 |
+
* @param string $dirPath Path to dir. It must exist in order for symlinks to be expanded.
|
211 |
+
*/
|
212 |
+
private static function isFilePathWithinExistingDirPath($filePath, $dirPath)
|
213 |
+
{
|
214 |
+
// sanity-check input. It must be a valid absolute filepath. It is allowed to be non-existing
|
215 |
+
self::absPath($filePath);
|
216 |
+
|
217 |
+
// sanity-check dir and that it exists.
|
218 |
+
self::absPathExistsAndIsDir($dirPath);
|
219 |
+
|
220 |
+
return PathHelper::isFilePathWithinDirPath($filePath, $dirPath);
|
221 |
+
}
|
222 |
+
|
223 |
+
/**
|
224 |
+
* Look if filepath is within multiple dir paths.
|
225 |
+
* Also tries expanding symlinks
|
226 |
+
*
|
227 |
+
* @param string $input Path to file. It may be non-existing.
|
228 |
+
* @param array $roots Allowed root dirs. Note that they must exist in order for symlinks to be expanded.
|
229 |
+
*/
|
230 |
+
public static function filePathWithinOneOfTheseRoots($input, $roots, $errorMsg = 'The path is outside allowed roots.')
|
231 |
+
{
|
232 |
+
self::absPath($input);
|
233 |
+
|
234 |
+
foreach ($roots as $root) {
|
235 |
+
if (self::isFilePathWithinExistingDirPath($input, $root)) {
|
236 |
+
return $input;
|
237 |
}
|
|
|
238 |
}
|
239 |
+
self::fail($errorMsg, $input);
|
240 |
+
}
|
241 |
+
|
242 |
+
/*
|
243 |
+
public static function sourcePath($input, $errorMsg = 'The source path is outside allowed roots. It is only allowed to convert images that resides in: home dir, content path, upload dir and plugin dir.')
|
244 |
+
{
|
245 |
+
$validPaths = [
|
246 |
+
Paths::getHomeDirAbs(),
|
247 |
+
Paths::getIndexDirAbs(),
|
248 |
+
Paths::getContentDirAbs(),
|
249 |
+
Paths::getUploadDirAbs(),
|
250 |
+
Paths::getPluginDirAbs()
|
251 |
+
];
|
252 |
+
return self::filePathWithinOneOfTheseRoots($input, $validPaths, $errorMsg);
|
253 |
}
|
254 |
|
255 |
+
public static function destinationPath($input, $errorMsg = 'The destination path is outside allowed roots. The webps may only be stored in the upload folder and in the folder that WebP Express stores converted images in')
|
256 |
+
{
|
257 |
+
self::absPath($input);
|
258 |
+
|
259 |
+
// Webp Express only store converted images in upload folder and in its "webp-images" folder
|
260 |
+
// Check that destination path is within one of these.
|
261 |
+
$validPaths = [
|
262 |
+
'/var/www/webp-express-tests/we1'
|
263 |
+
//Paths::getUploadDirAbs(),
|
264 |
+
//Paths::getWebPExpressContentDirRel() . '/webp-images'
|
265 |
+
];
|
266 |
+
return self::filePathWithinOneOfTheseRoots($input, $validPaths, $errorMsg);
|
267 |
+
}*/
|
268 |
+
|
269 |
+
|
270 |
/**
|
271 |
* Test that absolute path is in document root.
|
272 |
*
|
273 |
+
* TODO: Instead of this method, we shoud check
|
274 |
+
*
|
275 |
+
*
|
276 |
* It is acceptable if the absolute path does not exist
|
277 |
*/
|
278 |
public static function absPathIsInDocRoot($input, $errorMsg = 'Path is outside document root')
|
284 |
$docRoot = self::absPathExistsAndIsDir($docRoot);
|
285 |
|
286 |
// Use realpath to expand symbolic links and check if it exists
|
287 |
+
$docRootSymLinksExpanded = @realpath($docRoot);
|
288 |
if ($docRootSymLinksExpanded === false) {
|
289 |
+
$errorMsg = 'Cannot find document root';
|
290 |
+
self::fail($errorMsg, $input);
|
291 |
}
|
292 |
$docRootSymLinksExpanded = rtrim($docRootSymLinksExpanded, '/');
|
293 |
$docRootSymLinksExpanded = self::absPathExists($docRootSymLinksExpanded, 'Document root does not exist!');
|
303 |
return $input;
|
304 |
}
|
305 |
|
306 |
+
public static function absPathExists($input, $errorMsg = 'Path does not exist or it is outside restricted basedir')
|
307 |
{
|
308 |
self::absPath($input);
|
309 |
if (@!file_exists($input)) {
|
310 |
+
// TODO: We might be able to detect if the problem is that the path does not exist or if the problem
|
311 |
+
// is that it is outside restricted basedir.
|
312 |
+
// ie by creating an error handler or inspecting the php ini "open_basedir" setting
|
313 |
+
self::fail($errorMsg, $input);
|
314 |
}
|
315 |
return $input;
|
316 |
}
|
319 |
$input,
|
320 |
$errorMsg = 'Path points to a file (it should point to a directory)'
|
321 |
) {
|
322 |
+
self::absPathExists($input, 'Directory does not exist or is outside restricted basedir');
|
323 |
if (!is_dir($input)) {
|
324 |
+
self::fail($errorMsg, $input);
|
325 |
}
|
326 |
return $input;
|
327 |
}
|
330 |
$input,
|
331 |
$errorMsg = 'Path points to a directory (it should not do that)'
|
332 |
) {
|
333 |
+
self::absPathExists($input, 'File does not exist or is outside restricted basedir');
|
334 |
if (@is_dir($input)) {
|
335 |
+
self::fail($errorMsg, $input);
|
336 |
}
|
337 |
return $input;
|
338 |
}
|
358 |
self::noNUL($input);
|
359 |
self::mustBeString($input);
|
360 |
if (!preg_match($pattern, $input)) {
|
361 |
+
self::fail($errorMsg, $input);
|
362 |
}
|
363 |
return $input;
|
364 |
}
|
369 |
self::mustBeString($input);
|
370 |
self::notEmpty($input);
|
371 |
if ((strpos($input, '[') !== 0) || (!is_array(json_decode($input)))) {
|
372 |
+
self::fail($errorMsg, $input);
|
373 |
}
|
374 |
return $input;
|
375 |
}
|
380 |
self::mustBeString($input);
|
381 |
self::notEmpty($input);
|
382 |
if ((strpos($input, '{') !== 0) || (!is_object(json_decode($input)))) {
|
383 |
+
self::fail($errorMsg, $input);
|
384 |
}
|
385 |
return $input;
|
386 |
}
|
lib/classes/SelfTest.php
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
class SelfTest
|
6 |
+
{
|
7 |
+
|
8 |
+
private static $next;
|
9 |
+
|
10 |
+
public static function allInfo()
|
11 |
+
{
|
12 |
+
self::$next = 'done';
|
13 |
+
$config = Config::loadConfigAndFix(false);
|
14 |
+
return SelfTestHelper::allInfo($config);
|
15 |
+
}
|
16 |
+
|
17 |
+
|
18 |
+
public static function systemInfo()
|
19 |
+
{
|
20 |
+
self::$next = 'configInfo';
|
21 |
+
return SelfTestHelper::systemInfo();
|
22 |
+
}
|
23 |
+
|
24 |
+
public static function configInfo()
|
25 |
+
{
|
26 |
+
self::$next = 'capabilityTests';
|
27 |
+
$config = Config::loadConfigAndFix(false);
|
28 |
+
return SelfTestHelper::configInfo($config);
|
29 |
+
}
|
30 |
+
|
31 |
+
public static function capabilityTests()
|
32 |
+
{
|
33 |
+
self::$next = 'done';
|
34 |
+
$config = Config::loadConfigAndFix(false);
|
35 |
+
return SelfTestHelper::capabilityTests($config);
|
36 |
+
}
|
37 |
+
|
38 |
+
public static function redirectToExisting()
|
39 |
+
{
|
40 |
+
self::$next = 'done';
|
41 |
+
list ($success, $result) = SelfTestRedirectToExisting::runTest();
|
42 |
+
return $result;
|
43 |
+
/*
|
44 |
+
$result = [];
|
45 |
+
$result[] = '# Redirection tests';
|
46 |
+
$modRewriteWorking = CapabilityTest::modRewriteWorking();
|
47 |
+
$modHeaderWorking = CapabilityTest::modHeaderWorking();
|
48 |
+
|
49 |
+
if (($modRewriteWorking === false) && ($modHeaderWorking)) {
|
50 |
+
//$result[] = 'mod_rewrite is not working';
|
51 |
+
|
52 |
+
if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) {
|
53 |
+
|
54 |
+
$result[] = 'You are on Nginx and the rules that WebP Express stores in the .htaccess files does not ' .
|
55 |
+
'have any effect. '
|
56 |
+
|
57 |
+
}
|
58 |
+
// if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
return [$result, 'done'];*/
|
63 |
+
}
|
64 |
+
|
65 |
+
public static function redirectToConverter()
|
66 |
+
{
|
67 |
+
self::$next = 'done';
|
68 |
+
list ($success, $result) = SelfTestRedirectToConverter::runTest();
|
69 |
+
return $result;
|
70 |
+
}
|
71 |
+
|
72 |
+
public static function redirectToWebPRealizer()
|
73 |
+
{
|
74 |
+
self::$next = 'done';
|
75 |
+
list ($success, $result) = SelfTestRedirectToWebPRealizer::runTest();
|
76 |
+
return $result;
|
77 |
+
}
|
78 |
+
|
79 |
+
|
80 |
+
public static function processAjax()
|
81 |
+
{
|
82 |
+
if (!check_ajax_referer('webpexpress-ajax-self-test-nonce', 'nonce', false)) {
|
83 |
+
wp_send_json_error('Invalid security nonce (it has probably expired - try refreshing)');
|
84 |
+
wp_die();
|
85 |
+
}
|
86 |
+
|
87 |
+
// Check input
|
88 |
+
// --------------
|
89 |
+
try {
|
90 |
+
// Check "testId"
|
91 |
+
$checking = '"testId" argument';
|
92 |
+
Validate::postHasKey('testId');
|
93 |
+
|
94 |
+
$testId = sanitize_text_field(stripslashes($_POST['testId']));
|
95 |
+
|
96 |
+
} catch (Exception $e) {
|
97 |
+
wp_send_json_error('Validation failed for ' . $checking . ': '. $e->getMessage());
|
98 |
+
wp_die();
|
99 |
+
}
|
100 |
+
$result = '';
|
101 |
+
if (method_exists(__CLASS__, $testId)) {
|
102 |
+
|
103 |
+
// The following call sets self::$next.
|
104 |
+
$result = call_user_func(array(__CLASS__, $testId));
|
105 |
+
} else {
|
106 |
+
$result = ['Unknown test: ' . $testId];
|
107 |
+
self::$next = 'break';
|
108 |
+
}
|
109 |
+
|
110 |
+
$response = [
|
111 |
+
'result' => $result,
|
112 |
+
'next' => self::$next
|
113 |
+
];
|
114 |
+
echo json_encode($response, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT);
|
115 |
+
wp_die();
|
116 |
+
}
|
117 |
+
|
118 |
+
}
|
lib/classes/SelfTestHelper.php
ADDED
@@ -0,0 +1,535 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
use \WebPExpress\Paths;
|
6 |
+
|
7 |
+
class SelfTestHelper
|
8 |
+
{
|
9 |
+
|
10 |
+
public static function deleteFilesInDir($dir, $filePattern = "*")
|
11 |
+
{
|
12 |
+
foreach (glob($dir . DIRECTORY_SEPARATOR . $filePattern) as $filename) {
|
13 |
+
unlink($filename);
|
14 |
+
}
|
15 |
+
}
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Remove files in dir and the dir. Does not remove files recursively.
|
19 |
+
*/
|
20 |
+
public static function deleteDir($dir)
|
21 |
+
{
|
22 |
+
if (@file_exists($dir)) {
|
23 |
+
self::deleteFilesInDir($dir);
|
24 |
+
rmdir($dir);
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
|
29 |
+
public static function deleteTestImagesInFolder($rootId)
|
30 |
+
{
|
31 |
+
$testDir = Paths::getAbsDirById($rootId) . '/webp-express-test-images';
|
32 |
+
self::deleteDir($testDir);
|
33 |
+
}
|
34 |
+
|
35 |
+
public static function cleanUpTestImages($rootId, $config)
|
36 |
+
{
|
37 |
+
// Clean up test images in source folder
|
38 |
+
self::deleteTestImagesInFolder($rootId);
|
39 |
+
|
40 |
+
// Clean up dummy webp images in cache folder for the root
|
41 |
+
$cacheDirForRoot = Paths::getCacheDirForImageRoot(
|
42 |
+
$config['destination-folder'],
|
43 |
+
$config['destination-structure'],
|
44 |
+
$rootId
|
45 |
+
);
|
46 |
+
|
47 |
+
$testDir = $cacheDirForRoot . '/webp-express-test-images';
|
48 |
+
self::deleteDir($testDir);
|
49 |
+
}
|
50 |
+
|
51 |
+
public static function copyFile($source, $destination)
|
52 |
+
{
|
53 |
+
$result = [];
|
54 |
+
if (@copy($source, $destination)) {
|
55 |
+
return [true, $result];
|
56 |
+
} else {
|
57 |
+
$result[] = 'Failed to copy *' . $source . '* to *' . $destination . '*';
|
58 |
+
if (!@file_exists($source)) {
|
59 |
+
$result[] = 'The source file was not found';
|
60 |
+
} else {
|
61 |
+
if (!@file_exists(dirname($destination))) {
|
62 |
+
$result[] = 'The destination folder does not exist!';
|
63 |
+
} else {
|
64 |
+
$result[] = 'This is probably a permission issue. Check that your webserver has permission to ' .
|
65 |
+
'write files in the directory (*' . dirname($destination) . '*)';
|
66 |
+
}
|
67 |
+
}
|
68 |
+
return [false, $result];
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
public static function randomDigitsAndLetters($length)
|
73 |
+
{
|
74 |
+
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
75 |
+
$charactersLength = strlen($characters);
|
76 |
+
$randomString = '';
|
77 |
+
for ($i = 0; $i < $length; $i++) {
|
78 |
+
$randomString .= $characters[rand(0, $charactersLength - 1)];
|
79 |
+
}
|
80 |
+
return $randomString;
|
81 |
+
}
|
82 |
+
|
83 |
+
public static function copyTestImageToRoot($rootId, $imageType = 'jpeg')
|
84 |
+
{
|
85 |
+
// TODO: Copy to a subfolder instead
|
86 |
+
// TODO: Use smaller jpeg / pngs please.
|
87 |
+
$result = [];
|
88 |
+
switch ($imageType) {
|
89 |
+
case 'jpeg':
|
90 |
+
$fileNameToCopy = 'very-small.jpg';
|
91 |
+
break;
|
92 |
+
case 'png':
|
93 |
+
$fileNameToCopy = 'test.png';
|
94 |
+
break;
|
95 |
+
}
|
96 |
+
$testSource = Paths::getPluginDirAbs() . '/webp-express/test/' . $fileNameToCopy;
|
97 |
+
$filenameOfDestination = self::randomDigitsAndLetters(6) . '.' . $imageType;
|
98 |
+
$result[] = 'Copying ' . strtoupper($imageType) . ' to ' . $rootId . ' folder (*webp-express-test-images/' . $filenameOfDestination . '*)';
|
99 |
+
|
100 |
+
$destDir = Paths::getAbsDirById($rootId) . '/webp-express-test-images';
|
101 |
+
$destination = $destDir . '/' . $filenameOfDestination;
|
102 |
+
|
103 |
+
if (!@file_exists($destDir)) {
|
104 |
+
if (!@mkdir($destDir)) {
|
105 |
+
$result[count($result) - 1] .= '. FAILED';
|
106 |
+
$result[] = 'Failed to create folder for test images: ' . $destDir;
|
107 |
+
return [$result, false, ''];
|
108 |
+
}
|
109 |
+
}
|
110 |
+
|
111 |
+
list($success, $errors) = self::copyFile($testSource, $destination);
|
112 |
+
if (!$success) {
|
113 |
+
$result[count($result) - 1] .= '. FAILED';
|
114 |
+
$result = array_merge($result, $errors);
|
115 |
+
return [$result, false, ''];
|
116 |
+
} else {
|
117 |
+
$result[count($result) - 1] .= '. ok!';
|
118 |
+
$result[] = 'We now have a ' . $imageType . ' stored here:';
|
119 |
+
$result[] = '*' . $destination . '*';
|
120 |
+
}
|
121 |
+
return [$result, true, $filenameOfDestination];
|
122 |
+
}
|
123 |
+
|
124 |
+
public static function copyTestImageToUploadFolder($imageType = 'jpeg')
|
125 |
+
{
|
126 |
+
return self::copyTestImageToRoot('uploads', $imageType);
|
127 |
+
}
|
128 |
+
|
129 |
+
public static function copyDummyWebPToCacheFolder($rootId, $destinationFolder, $destinationExtension, $destinationStructure, $sourceFileName, $imageType = 'jpeg')
|
130 |
+
{
|
131 |
+
$result = [];
|
132 |
+
$dummyWebP = Paths::getPluginDirAbs() . '/webp-express/test/test.jpg.webp';
|
133 |
+
|
134 |
+
$result[] = 'Copying dummy webp to the cache root for ' . $rootId;
|
135 |
+
$destDir = Paths::getCacheDirForImageRoot($destinationFolder, $destinationStructure, $rootId);
|
136 |
+
if (!file_exists($destDir)) {
|
137 |
+
$result[] = 'The folder did not exist. Creating folder at: ' . $destinationDir;
|
138 |
+
if (!mkdir($destDir, 0777, true)) {
|
139 |
+
$result[] = 'Failed creating folder!';
|
140 |
+
return [$result, false, ''];
|
141 |
+
}
|
142 |
+
}
|
143 |
+
$destDir .= '/webp-express-test-images';
|
144 |
+
if (!file_exists($destDir)) {
|
145 |
+
if (!mkdir($destDir, 0755, false)) {
|
146 |
+
$result[] = 'Failed creating the folder for the test images:';
|
147 |
+
$result[] = $destDir;
|
148 |
+
$result[] = 'To run this test, you must grant write permissions';
|
149 |
+
return [$result, false, ''];
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
$filenameOfDestination = ConvertHelperIndependent::appendOrSetExtension(
|
154 |
+
$sourceFileName,
|
155 |
+
$destinationFolder,
|
156 |
+
$destinationExtension,
|
157 |
+
($rootId == 'uploads')
|
158 |
+
);
|
159 |
+
|
160 |
+
//$filenameOfDestination = $destinationFileNameNoExt . ($destinationExtension == 'append' ? '.' . $imageType : '') . '.webp';
|
161 |
+
$destination = $destDir . '/' . $filenameOfDestination;
|
162 |
+
|
163 |
+
list($success, $errors) = self::copyFile($dummyWebP, $destination);
|
164 |
+
if (!$success) {
|
165 |
+
$result[count($result) - 1] .= '. FAILED';
|
166 |
+
$result = array_merge($result, $errors);
|
167 |
+
return [$result, false, ''];
|
168 |
+
} else {
|
169 |
+
$result[count($result) - 1] .= '. ok!';
|
170 |
+
$result[] = 'We now have a webp file stored here:';
|
171 |
+
$result[] = '*' . $destination . '*';
|
172 |
+
$result[] = '';
|
173 |
+
}
|
174 |
+
return [$result, true, $destination];
|
175 |
+
}
|
176 |
+
|
177 |
+
public static function remoteGet($requestUrl, $args = [])
|
178 |
+
{
|
179 |
+
$result = [];
|
180 |
+
$return = wp_remote_get($requestUrl, $args);
|
181 |
+
if (is_wp_error($return)) {
|
182 |
+
$result[] = 'Request URL: ' . $requestUrl;
|
183 |
+
$result[] = 'The remote request errored!';
|
184 |
+
return [false, $result, [], $return];
|
185 |
+
}
|
186 |
+
if ($return['response']['code'] != '200') {
|
187 |
+
//$result[count($result) - 1] .= '. FAILED';
|
188 |
+
$result[] = 'Request URL: ' . $requestUrl;
|
189 |
+
$result[] = 'Response: ' . $return['response']['code'] . ' ' . $return['response']['message'];
|
190 |
+
|
191 |
+
if (isset($return['headers'])) {
|
192 |
+
$result = array_merge($result, SelfTestHelper::printHeaders($return['headers']));
|
193 |
+
} else {
|
194 |
+
$return['headers'] = [];
|
195 |
+
}
|
196 |
+
return [false, $result, $return['headers'], $return];
|
197 |
+
}
|
198 |
+
return [true, $result, $return['headers'], $return];
|
199 |
+
}
|
200 |
+
|
201 |
+
public static function hasHeaderContaining($headers, $headerToInspect, $containString)
|
202 |
+
{
|
203 |
+
if (!isset($headers[$headerToInspect])) {
|
204 |
+
return false;
|
205 |
+
}
|
206 |
+
|
207 |
+
// If there are multiple headers, check all
|
208 |
+
if (gettype($headers[$headerToInspect]) == 'string') {
|
209 |
+
$h = [$headers[$headerToInspect]];
|
210 |
+
} else {
|
211 |
+
$h = $headers[$headerToInspect];
|
212 |
+
}
|
213 |
+
foreach ($h as $headerValue) {
|
214 |
+
if (stripos($headerValue, $containString) !== false) {
|
215 |
+
return true;
|
216 |
+
}
|
217 |
+
}
|
218 |
+
return false;
|
219 |
+
}
|
220 |
+
|
221 |
+
public static function hasVaryAcceptHeader($headers)
|
222 |
+
{
|
223 |
+
if (!isset($headers['vary'])) {
|
224 |
+
return false;
|
225 |
+
}
|
226 |
+
|
227 |
+
// There may be multiple Vary headers. Or they might be combined in one.
|
228 |
+
// Both are acceptable, according to https://stackoverflow.com/a/28799169/842756
|
229 |
+
if (gettype($headers['vary']) == 'string') {
|
230 |
+
$varyHeaders = [$headers['vary']];
|
231 |
+
} else {
|
232 |
+
$varyHeaders = $headers['vary'];
|
233 |
+
}
|
234 |
+
foreach ($varyHeaders as $headerValue) {
|
235 |
+
$values = explode(',', $headerValue);
|
236 |
+
foreach ($values as $value) {
|
237 |
+
if (strtolower($value) == 'accept') {
|
238 |
+
return true;
|
239 |
+
}
|
240 |
+
}
|
241 |
+
}
|
242 |
+
return false;
|
243 |
+
}
|
244 |
+
|
245 |
+
public static function hasCacheControlOrExpiresHeader($headers)
|
246 |
+
{
|
247 |
+
if (isset($headers['cache-control'])) {
|
248 |
+
return true;
|
249 |
+
}
|
250 |
+
if (isset($headers['expires'])) {
|
251 |
+
return true;
|
252 |
+
}
|
253 |
+
return false;
|
254 |
+
}
|
255 |
+
|
256 |
+
|
257 |
+
public static function flattenHeaders($headers)
|
258 |
+
{
|
259 |
+
$result = [];
|
260 |
+
foreach ($headers as $headerName => $headerValue) {
|
261 |
+
if (gettype($headerValue) == 'array') {
|
262 |
+
foreach ($headerValue as $i => $value) {
|
263 |
+
$result[] = [$headerName, $value];
|
264 |
+
}
|
265 |
+
} else {
|
266 |
+
$result[] = [$headerName, $headerValue];
|
267 |
+
}
|
268 |
+
}
|
269 |
+
return $result;
|
270 |
+
}
|
271 |
+
|
272 |
+
public static function printHeaders($headers)
|
273 |
+
{
|
274 |
+
$result = [];
|
275 |
+
$result[] = '#### Response headers:';
|
276 |
+
|
277 |
+
$headersFlat = self::flattenHeaders($headers);
|
278 |
+
//
|
279 |
+
foreach ($headersFlat as $i => list($headerName, $headerValue)) {
|
280 |
+
if ($headerName == 'x-webp-express-error') {
|
281 |
+
$headerValue = '**' . $headerValue . '**{: .error}';
|
282 |
+
}
|
283 |
+
$result[] = '- ' . $headerName . ': ' . $headerValue;
|
284 |
+
}
|
285 |
+
$result[] = '';
|
286 |
+
return $result;
|
287 |
+
}
|
288 |
+
|
289 |
+
private static function trueFalseNullString($var)
|
290 |
+
{
|
291 |
+
if ($var === true) {
|
292 |
+
return 'yes';
|
293 |
+
}
|
294 |
+
if ($var === false) {
|
295 |
+
return 'no';
|
296 |
+
}
|
297 |
+
return 'could not be determined';
|
298 |
+
}
|
299 |
+
|
300 |
+
public static function systemInfo()
|
301 |
+
{
|
302 |
+
$result = [];
|
303 |
+
$result[] = '#### System info:';
|
304 |
+
$result[] = '- PHP version: ' . phpversion();
|
305 |
+
$result[] = '- OS: ' . PHP_OS;
|
306 |
+
$result[] = '- Server software: ' . $_SERVER["SERVER_SOFTWARE"];
|
307 |
+
$result[] = '- Document Root status: ' . Paths::docRootStatusText();
|
308 |
+
if (PathHelper::isDocRootAvailable()) {
|
309 |
+
$result[] = '- Document Root: ' . $_SERVER['DOCUMENT_ROOT'];
|
310 |
+
}
|
311 |
+
if (PathHelper::isDocRootAvailableAndResolvable()) {
|
312 |
+
if ($_SERVER['DOCUMENT_ROOT'] != realpath($_SERVER['DOCUMENT_ROOT'])) {
|
313 |
+
$result[] = '- Document Root (symlinked resolved): ' . realpath($_SERVER['DOCUMENT_ROOT']);
|
314 |
+
}
|
315 |
+
}
|
316 |
+
|
317 |
+
$result[] = '- Document Root: ' . Paths::docRootStatusText();
|
318 |
+
$result[] = '- Apache module "mod_rewrite" enabled?: ' . self::trueFalseNullString(PlatformInfo::gotApacheModule('mod_rewrite'));
|
319 |
+
$result[] = '- Apache module "mod_headers" enabled?: ' . self::trueFalseNullString(PlatformInfo::gotApacheModule('mod_headers'));
|
320 |
+
return $result;
|
321 |
+
}
|
322 |
+
|
323 |
+
public static function wordpressInfo()
|
324 |
+
{
|
325 |
+
$result = [];
|
326 |
+
$result[] = '#### Wordpress info:';
|
327 |
+
$result[] = '- Version: ' . get_bloginfo('version');
|
328 |
+
$result[] = '- Multisite?: ' . self::trueFalseNullString(is_multisite());
|
329 |
+
$result[] = '- Is wp-content moved?: ' . self::trueFalseNullString(Paths::isWPContentDirMoved());
|
330 |
+
$result[] = '- Is uploads moved out of wp-content?: ' . self::trueFalseNullString(Paths::isUploadDirMovedOutOfWPContentDir());
|
331 |
+
$result[] = '- Is plugins moved out of wp-content?: ' . self::trueFalseNullString(Paths::isPluginDirMovedOutOfWpContent());
|
332 |
+
|
333 |
+
$result[] = '';
|
334 |
+
|
335 |
+
$result[] = '#### Image roots:';
|
336 |
+
foreach (Paths::getImageRootIds() as $rootId) {
|
337 |
+
$absDir = Paths::getAbsDirById($rootId);
|
338 |
+
|
339 |
+
if (PathHelper::pathExistsAndIsResolvable($absDir) && ($absDir != realpath($absDir))) {
|
340 |
+
$result[] = '*' . $rootId . '*: ' . $absDir . ' (resolved for symlinks: ' . realpath($absDir) . ')';
|
341 |
+
} else {
|
342 |
+
$result[] = '*' . $rootId . '*: ' . $absDir;
|
343 |
+
|
344 |
+
}
|
345 |
+
}
|
346 |
+
|
347 |
+
$result[] = '#### Image roots (relative to document root)';
|
348 |
+
foreach (Paths::getImageRootIds() as $rootId) {
|
349 |
+
$absPath = Paths::getAbsDirById($rootId);
|
350 |
+
if (PathHelper::canCalculateRelPathFromDocRootToDir($absPath)) {
|
351 |
+
$result[] = '*' . $rootId . '*: ' . PathHelper::getRelPathFromDocRootToDirNoDirectoryTraversalAllowed($absPath);
|
352 |
+
} else {
|
353 |
+
$result[] = '*' . $rootId . '*: ' . 'n/a (not within document root)';
|
354 |
+
}
|
355 |
+
}
|
356 |
+
|
357 |
+
return $result;
|
358 |
+
}
|
359 |
+
|
360 |
+
public static function configInfo($config)
|
361 |
+
{
|
362 |
+
$result = [];
|
363 |
+
$result[] = '#### WebP Express configuration info:';
|
364 |
+
$result[] = '- Destination folder: ' . $config['destination-folder'];
|
365 |
+
$result[] = '- Destination extension: ' . $config['destination-extension'];
|
366 |
+
$result[] = '- Destination structure: ' . $config['destination-structure'];
|
367 |
+
//$result[] = 'Image types: ' . ;
|
368 |
+
//$result[] = '';
|
369 |
+
$result[] = '(To view all configuration, take a look at the config file, which is stored in *' . Paths::getConfigFileName() . '*)';
|
370 |
+
return $result;
|
371 |
+
}
|
372 |
+
|
373 |
+
public static function htaccessInfo($config, $printRules = true)
|
374 |
+
{
|
375 |
+
$result = [];
|
376 |
+
//$result[] = '*.htaccess info:*';
|
377 |
+
//$result[] = '- Image roots with WebP Express rules: ' . implode(', ', HTAccess::getRootsWithWebPExpressRulesIn());
|
378 |
+
$result[] = '#### .htaccess files that WebP Express have placed rules in the following files:';
|
379 |
+
$rootIds = HTAccess::getRootsWithWebPExpressRulesIn();
|
380 |
+
foreach ($rootIds as $imageRootId) {
|
381 |
+
$result[] = '- ' . Paths::getAbsDirById($imageRootId) . '/.htaccess';
|
382 |
+
}
|
383 |
+
|
384 |
+
foreach ($rootIds as $imageRootId) {
|
385 |
+
$result = array_merge($result, self::rulesInImageRoot($config, $imageRootId));
|
386 |
+
}
|
387 |
+
|
388 |
+
return $result;
|
389 |
+
}
|
390 |
+
|
391 |
+
public static function rulesInImageRoot($config, $imageRootId)
|
392 |
+
{
|
393 |
+
$result = [];
|
394 |
+
$result[] = '#### WebP rules in *' . $imageRootId . '*:';
|
395 |
+
$file = Paths::getAbsDirById($imageRootId) . '/.htaccess';
|
396 |
+
if (!HTAccess::haveWeRulesInThisHTAccess($file)) {
|
397 |
+
$result[] = '**NONE!**{: .warn}';
|
398 |
+
} else {
|
399 |
+
$weRules = HTAccess::extractWebPExpressRulesFromHTAccess($file);
|
400 |
+
// remove unindented comments
|
401 |
+
//$weRules = preg_replace('/^\#\s[^\n\r]*[\n\r]+/ms', '', $weRules);
|
402 |
+
|
403 |
+
// remove comments in the beginning
|
404 |
+
$weRulesArr = preg_split("/\r\n|\n|\r/", $weRules); // https://stackoverflow.com/a/11165332/842756
|
405 |
+
while ((strlen($weRulesArr[0]) > 0) && ($weRulesArr[0][0] == '#')) {
|
406 |
+
array_shift($weRulesArr);
|
407 |
+
}
|
408 |
+
$weRules = implode("\n", $weRulesArr);
|
409 |
+
|
410 |
+
$result[] = '```' . $weRules . '```';
|
411 |
+
}
|
412 |
+
return $result;
|
413 |
+
}
|
414 |
+
|
415 |
+
public static function rulesInUpload($config)
|
416 |
+
{
|
417 |
+
return self::rulesInImageRoot($config, 'uploads');
|
418 |
+
}
|
419 |
+
|
420 |
+
public static function allInfo($config)
|
421 |
+
{
|
422 |
+
$result = [];
|
423 |
+
$result = array_merge($result, self::systemInfo());
|
424 |
+
$result = array_merge($result, self::wordpressInfo());
|
425 |
+
$result = array_merge($result, self::configInfo($config));
|
426 |
+
$result = array_merge($result, self::capabilityTests($config));
|
427 |
+
$result = array_merge($result, self::htaccessInfo($config, true));
|
428 |
+
//$result = array_merge($result, self::rulesInImageRoot($config, 'upload'));
|
429 |
+
//$result = array_merge($result, self::rulesInImageRoot($config, 'wp-content'));
|
430 |
+
return $result;
|
431 |
+
}
|
432 |
+
|
433 |
+
public static function capabilityTests($config)
|
434 |
+
{
|
435 |
+
$capTests = $config['base-htaccess-on-these-capability-tests'];
|
436 |
+
$result = [];
|
437 |
+
$result[] = '#### Live tests of .htaccess capabilities:';
|
438 |
+
/*$result[] = 'Exactly what you can do in a *.htaccess* depends on the server setup. WebP Express ' .
|
439 |
+
'makes some live tests to verify if a certain feature in fact works. This is done by creating ' .
|
440 |
+
'test files (*.htaccess* files and php files) in a dir inside the content dir and running these. ' .
|
441 |
+
'These test results are used when creating the rewrite rules. Here are the results:';*/
|
442 |
+
|
443 |
+
// $result[] = '';
|
444 |
+
$result[] = '- mod_rewrite working?: ' . self::trueFalseNullString(CapabilityTest::modRewriteWorking());
|
445 |
+
$result[] = '- mod_header working?: ' . self::trueFalseNullString($capTests['modHeaderWorking']);
|
446 |
+
/*$result[] = '- pass variable from *.htaccess* to script through header working?: ' .
|
447 |
+
self::trueFalseNullString($capTests['passThroughHeaderWorking']);*/
|
448 |
+
$result[] = '- passing variables from *.htaccess* to PHP script through environment variable working?: ' . self::trueFalseNullString($capTests['passThroughEnvWorking']);
|
449 |
+
return $result;
|
450 |
+
}
|
451 |
+
|
452 |
+
public static function diagnoseFailedRewrite($config)
|
453 |
+
{
|
454 |
+
if (($config['destination-structure'] == 'image-roots') && (!PathHelper::isDocRootAvailableAndResolvable())) {
|
455 |
+
$result[] = 'The problem is probably this combination:';
|
456 |
+
if (!PathHelper::isDocRootAvailable()) {
|
457 |
+
$result[] = '1. Your document root isn`t available';
|
458 |
+
} else {
|
459 |
+
$result[] = '1. Your document root isn`t resolvable for symlinks (it is probably subject to open_basedir restriction)';
|
460 |
+
}
|
461 |
+
$result[] = '2. Your document root is symlinked';
|
462 |
+
$result[] = '3. The wordpress function that tells the path of the uploads folder returns the symlink resolved path';
|
463 |
+
|
464 |
+
$result[] = 'I cannot check if your document root is in fact symlinked (as document root isnt resolvable). ' .
|
465 |
+
'But if it is, there you have it. The line beginning with "RewriteCond %{REQUEST_FILENAME}"" points to your resolved root, ' .
|
466 |
+
'but it should point to your symlinked root. WebP Express cannot do that for you because it cannot discover what the symlink is. ' .
|
467 |
+
'Try changing the line manually. When it works, you can move the rules outside the WebP Express block so they dont get ' .
|
468 |
+
'overwritten. OR you can change your server configuration (document root / open_basedir restrictions)';
|
469 |
+
}
|
470 |
+
|
471 |
+
//$result[] = '## Diagnosing';
|
472 |
+
if (PlatformInfo::isNginx()) {
|
473 |
+
// Nginx
|
474 |
+
$result[] = 'Notice that you are on Nginx and the rules that WebP Express stores in the *.htaccess* files probably does not ' .
|
475 |
+
'have any effect. ';
|
476 |
+
$result[] = 'Please read the "I am on Nginx" section in the FAQ (https://wordpress.org/plugins/webp-express/)';
|
477 |
+
$result[] = 'And did you remember to restart the nginx service after updating the configuration?';
|
478 |
+
|
479 |
+
$result[] = 'PS: If you cannot get the redirect to work, you can simply rely on Alter HTML as described in the FAQ.';
|
480 |
+
return $result;
|
481 |
+
}
|
482 |
+
|
483 |
+
$modRewriteWorking = CapabilityTest::modRewriteWorking();
|
484 |
+
if ($modRewriteWorking !== null) {
|
485 |
+
$result[] = 'Running a special designed capability test to test if rewriting works with *.htaccess* files';
|
486 |
+
}
|
487 |
+
if ($modRewriteWorking === true) {
|
488 |
+
$result[] = 'Result: Yes, rewriting works.';
|
489 |
+
$result[] = 'It seems something is wrong with the *.htaccess* rules then. You could try ' .
|
490 |
+
'to change "Destination structure" - the rules there are quite different.';
|
491 |
+
$result[] = 'It could also be that the server has cached the configuration a while. Some servers ' .
|
492 |
+
'does that. In that case, simply give it a few minutes and try again.';
|
493 |
+
} elseif ($modRewriteWorking === false) {
|
494 |
+
$result[] = 'Result: No, rewriting does not seem to work within *.htaccess* rules.';
|
495 |
+
if (PlatformInfo::definitelyNotGotModRewrite()) {
|
496 |
+
$result[] = 'It actually seems "mod_write" is disabled on your server. ' .
|
497 |
+
'**You must enable mod_rewrite on the server**';
|
498 |
+
} elseif (PlatformInfo::definitelyGotApacheModule('mod_rewrite')) {
|
499 |
+
$result[] = 'However, "mod_write" *is* enabled on your server. This seems to indicate that ' .
|
500 |
+
'*.htaccess* files has been disabled for configuration on your server. ' .
|
501 |
+
'In that case, you need to copy the WebP Express rules from the *.htaccess* files into your virtual host configuration files. ' .
|
502 |
+
'(WebP Express generates multiple *.htaccess* files. Look in the upload folder, the wp-content folder, etc).';
|
503 |
+
$result[] = 'It could however alse simply be that your server simply needs some time. ' .
|
504 |
+
'Some servers caches the *.htaccess* rules for a bit. In that case, simply give it a few minutes and try again.';
|
505 |
+
} else {
|
506 |
+
$result[] = 'However, this could be due to your server being a bit slow on picking up changes in *.htaccess*.' .
|
507 |
+
'Give it a few minutes and try again.';
|
508 |
+
}
|
509 |
+
} else {
|
510 |
+
// The mod_rewrite test could not conclude anything.
|
511 |
+
if (PlatformInfo::definitelyNotGotApacheModule('mod_rewrite')) {
|
512 |
+
$result[] = 'It actually seems "mod_write" is disabled on your server. ' .
|
513 |
+
'**You must enable mod_rewrite on the server**';
|
514 |
+
} elseif (PlatformInfo::definitelyGotApacheModule('mod_rewrite')) {
|
515 |
+
$result[] = '"mod_write" is enabled on your server, so rewriting ought to work. ' .
|
516 |
+
'However, it could be that your server setup has disabled *.htaccess* files for configuration. ' .
|
517 |
+
'In that case, you need to copy the WebP Express rules from the *.htaccess* files into your virtual host configuration files. ' .
|
518 |
+
'(WebP Express generates multiple *.htaccess* files. Look in the upload folder, the wp-content folder, etc). ';
|
519 |
+
} else {
|
520 |
+
$result[] = 'It seems something is wrong with the *.htaccess* rules. ';
|
521 |
+
$result[] = 'Or perhaps the server has cached the configuration a while. Some servers ' .
|
522 |
+
'does that. In that case, simply give it a few minutes and try again.';
|
523 |
+
}
|
524 |
+
}
|
525 |
+
$result[] = 'Note that if you cannot get redirection to work, you can switch to "CDN friendly" mode and ' .
|
526 |
+
'rely on the "Alter HTML" functionality to point to the webp images. If you do a bulk conversion ' .
|
527 |
+
'and make sure that "Convert upon upload" is activated, you should be all set. Alter HTML even handles ' .
|
528 |
+
'inline css (unless you select "picture tag" syntax). It does however not handle images in external css or ' .
|
529 |
+
'which is added dynamically with javascript.';
|
530 |
+
|
531 |
+
$result[] = '## Info for manually diagnosing';
|
532 |
+
$result = array_merge($result, self::allInfo($config));
|
533 |
+
return $result;
|
534 |
+
}
|
535 |
+
}
|
lib/classes/SelfTestRedirectAbstract.php
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
abstract class SelfTestRedirectAbstract
|
6 |
+
{
|
7 |
+
protected $config;
|
8 |
+
|
9 |
+
public function __construct($config) {
|
10 |
+
$this->config = $config;
|
11 |
+
}
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Run test for either jpeg or png
|
15 |
+
*
|
16 |
+
* @param string $rootId (ie "uploads" or "themes")
|
17 |
+
* @param string $imageType ("jpeg" or "png")
|
18 |
+
* @return array [$success, $result, $createdTestFiles]
|
19 |
+
*/
|
20 |
+
abstract protected function runTestForImageType($rootId, $imageType);
|
21 |
+
|
22 |
+
abstract protected function getSuccessMessage();
|
23 |
+
|
24 |
+
private function doRunTestForRoot($rootId)
|
25 |
+
{
|
26 |
+
// return [true, ['hello'], false];
|
27 |
+
// return [false, SelfTestHelper::diagnoseFailedRewrite($this->config), false];
|
28 |
+
|
29 |
+
$result = [];
|
30 |
+
|
31 |
+
//$result[] = '*hello* with *you* and **you**. ok! FAILED';
|
32 |
+
$result[] = '## ' . $rootId;
|
33 |
+
//$result[] = 'This test examines image responses "from the outside".';
|
34 |
+
|
35 |
+
$createdTestFiles = false;
|
36 |
+
|
37 |
+
if ($this->config['image-types'] & 1) {
|
38 |
+
list($success, $subResult, $createdTestFiles) = $this->runTestForImageType($rootId, 'jpeg');
|
39 |
+
$result = array_merge($result, $subResult);
|
40 |
+
|
41 |
+
if ($success) {
|
42 |
+
if ($this->config['image-types'] & 2) {
|
43 |
+
$result[] = '### Performing same tests for PNG';
|
44 |
+
list($success, $subResult, $createdTestFiles2) = $this->runTestForImageType($rootId, 'png');
|
45 |
+
$createdTestFiles = $createdTestFiles || $createdTestFiles2;
|
46 |
+
if ($success) {
|
47 |
+
//$result[count($result) - 1] .= '. **ok**{: .ok}';
|
48 |
+
$result[] .= 'All tests passed for PNG as well.';
|
49 |
+
$result[] = '(I shall spare you for the report, which is almost identical to the one above)';
|
50 |
+
} else {
|
51 |
+
$result = array_merge($result, $subResult);
|
52 |
+
}
|
53 |
+
}
|
54 |
+
}
|
55 |
+
} else {
|
56 |
+
list($success, $subResult, $createdTestFiles) = $this->runTestForImageType($rootId, 'png');
|
57 |
+
$result = array_merge($result, $subResult);
|
58 |
+
}
|
59 |
+
|
60 |
+
if ($success) {
|
61 |
+
$result[] = '### Results for ' . strtoupper($rootId);
|
62 |
+
|
63 |
+
$result[] = $this->getSuccessMessage();
|
64 |
+
}
|
65 |
+
return [true, $result, $createdTestFiles];
|
66 |
+
}
|
67 |
+
|
68 |
+
private function runTestForRoot($rootId)
|
69 |
+
{
|
70 |
+
// TODO: move that method to here
|
71 |
+
SelfTestHelper::cleanUpTestImages($rootId, $this->config);
|
72 |
+
|
73 |
+
// Run the actual test
|
74 |
+
list($success, $result, $createdTestFiles) = $this->doRunTestForRoot($rootId);
|
75 |
+
|
76 |
+
// Clean up test images again. We are very tidy around here
|
77 |
+
if ($createdTestFiles) {
|
78 |
+
$result[] = 'Deleting test images';
|
79 |
+
SelfTestHelper::cleanUpTestImages($rootId, $this->config);
|
80 |
+
}
|
81 |
+
|
82 |
+
return [$success, $result];
|
83 |
+
}
|
84 |
+
|
85 |
+
abstract protected function startupTests();
|
86 |
+
|
87 |
+
protected function startTest()
|
88 |
+
{
|
89 |
+
|
90 |
+
list($success, $result) = $this->startupTests();
|
91 |
+
|
92 |
+
if (!$success) {
|
93 |
+
return [false, $result];
|
94 |
+
}
|
95 |
+
|
96 |
+
if (!file_exists(Paths::getConfigFileName())) {
|
97 |
+
$result[] = 'Hold on. You need to save options before you can run this test. There is no config file yet.';
|
98 |
+
return [true, $result];
|
99 |
+
}
|
100 |
+
|
101 |
+
if ($this->config['image-types'] == 0) {
|
102 |
+
$result[] = 'No image types have been activated, nothing to test';
|
103 |
+
return [true, $result];
|
104 |
+
}
|
105 |
+
|
106 |
+
foreach ($this->config['scope'] as $rootId) {
|
107 |
+
list($success, $subResult) = $this->runTestForRoot($rootId);
|
108 |
+
$result = array_merge($result, $subResult);
|
109 |
+
}
|
110 |
+
//list($success, $result) = self::runTestForRoot('uploads', $this->config);
|
111 |
+
|
112 |
+
return [$success, $result];
|
113 |
+
}
|
114 |
+
|
115 |
+
}
|
lib/classes/SelfTestRedirectToConverter.php
ADDED
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
class SelfTestRedirectToConverter extends SelfTestRedirectAbstract
|
6 |
+
{
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Run test for either jpeg or png
|
10 |
+
*
|
11 |
+
* @param string $rootId (ie "uploads" or "themes")
|
12 |
+
* @param string $imageType ("jpeg" or "png")
|
13 |
+
* @return array [$success, $result, $createdTestFiles]
|
14 |
+
*/
|
15 |
+
protected function runTestForImageType($rootId, $imageType)
|
16 |
+
{
|
17 |
+
$result = [];
|
18 |
+
$createdTestFiles = false;
|
19 |
+
$noWarningsYet = true;
|
20 |
+
|
21 |
+
// Copy test image (jpeg)
|
22 |
+
list($subResult, $success, $sourceFileName) = SelfTestHelper::copyTestImageToRoot($rootId, $imageType);
|
23 |
+
$result = array_merge($result, $subResult);
|
24 |
+
if (!$success) {
|
25 |
+
$result[] = 'The test cannot be completed';
|
26 |
+
return [false, $result, $createdTestFiles];
|
27 |
+
}
|
28 |
+
$createdTestFiles = true;
|
29 |
+
|
30 |
+
$requestUrl = Paths::getUrlById($rootId) . '/webp-express-test-images/' . $sourceFileName;
|
31 |
+
|
32 |
+
$result[] = '### Lets check that browsers supporting webp gets a freshly converted WEBP ' .
|
33 |
+
'when the ' . $imageType . ' is requested';
|
34 |
+
$result[] = 'Making a HTTP request for the test image (pretending to be a client that supports webp, by setting the "Accept" header to "image/webp")';
|
35 |
+
$requestArgs = [
|
36 |
+
'headers' => [
|
37 |
+
'ACCEPT' => 'image/webp'
|
38 |
+
]
|
39 |
+
];
|
40 |
+
list($success, $errors, $headers, $return) = SelfTestHelper::remoteGet($requestUrl, $requestArgs);
|
41 |
+
|
42 |
+
if (!$success) {
|
43 |
+
$result[count($result) - 1] .= '. FAILED';
|
44 |
+
$result = array_merge($result, $errors);
|
45 |
+
$result[] = 'The test cannot be completed';
|
46 |
+
//$result[count($result) - 1] .= '. FAILED';
|
47 |
+
return [false, $result, $createdTestFiles];
|
48 |
+
}
|
49 |
+
//$result[count($result) - 1] .= '. ok!';
|
50 |
+
$result[] = '*' . $requestUrl . '*';
|
51 |
+
|
52 |
+
$result = array_merge($result, SelfTestHelper::printHeaders($headers));
|
53 |
+
|
54 |
+
if (!isset($headers['content-type'])) {
|
55 |
+
$result[] = 'Bummer. There is no "content-type" response header. The test FAILED';
|
56 |
+
return [false, $result, $createdTestFiles];
|
57 |
+
}
|
58 |
+
|
59 |
+
if ($headers['content-type'] == 'image/' . $imageType) {
|
60 |
+
$result[] = 'Bummer. As the "content-type" header reveals, we got the ' . $imageType . '.';
|
61 |
+
$result[] = 'The test **failed**{: .error}.';
|
62 |
+
$result[] = 'Now, what went wrong?';
|
63 |
+
|
64 |
+
if (isset($headers['x-webp-convert-log'])) {
|
65 |
+
//$result[] = 'Inspect the "x-webp-convert-log" headers above, and you ' .
|
66 |
+
// 'should have your answer (it is probably because you do not have any conversion methods working).';
|
67 |
+
if (SelfTestHelper::hasHeaderContaining($headers, 'x-webp-convert-log', 'Performing fail action: original')) {
|
68 |
+
$result[] = 'The answer lies in the "x-convert-log" response headers: ' .
|
69 |
+
'**The conversion failed**{: .error}. ';
|
70 |
+
}
|
71 |
+
} else {
|
72 |
+
$result[] = 'Well, there is indication that the redirection isnt working. ' .
|
73 |
+
'The PHP script should set "x-webp-convert-log" response headers, but there are none. ';
|
74 |
+
'While these headers could have been eaten in a Cloudflare-like setup, the problem is ';
|
75 |
+
'probably that the redirection simply failed';
|
76 |
+
|
77 |
+
$result[] = '### Diagnosing redirection problems';
|
78 |
+
$result = array_merge($result, SelfTestHelper::diagnoseFailedRewrite($this->config));
|
79 |
+
}
|
80 |
+
return [false, $result, $createdTestFiles];
|
81 |
+
}
|
82 |
+
|
83 |
+
if ($headers['content-type'] != 'image/webp') {
|
84 |
+
$result[] = 'However. As the "content-type" header reveals, we did not get a webp' .
|
85 |
+
'Surprisingly we got: "' . $headers['content-type'] . '"';
|
86 |
+
$result[] = 'The test FAILED.';
|
87 |
+
if (strpos($headers['content-type'], 'text/html') !== false) {
|
88 |
+
$result[] = 'Body:';
|
89 |
+
$result[] = print_r($return['body'], true);
|
90 |
+
}
|
91 |
+
return [false, $result, $createdTestFiles];
|
92 |
+
}
|
93 |
+
|
94 |
+
$result[] = 'Alrighty. We got a webp, and we got it from the PHP script. **Great!**{: .ok}';
|
95 |
+
|
96 |
+
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
97 |
+
$result[count($result) - 1] .= '. **BUT!**';
|
98 |
+
$result[] = '**Warning: We did not receive a Vary:Accept header. ' .
|
99 |
+
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
100 |
+
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
101 |
+
$noWarningsYet = false;
|
102 |
+
}
|
103 |
+
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
104 |
+
$result[] = '**Notice: No cache-control or expires header has been set. ' .
|
105 |
+
'It is recommended to do so. Set it nice and big once you are sure the webps have a good quality/compression compromise.**{: .warn}';
|
106 |
+
}
|
107 |
+
$result[] = '';
|
108 |
+
|
109 |
+
|
110 |
+
// Check browsers NOT supporting webp
|
111 |
+
// -----------------------------------
|
112 |
+
$result[] = '### Now lets check that browsers *not* supporting webp gets the ' . strtoupper($imageType);
|
113 |
+
$result[] = 'Making a HTTP request for the test image (without setting the "Accept" header)';
|
114 |
+
list($success, $errors, $headers) = SelfTestHelper::remoteGet($requestUrl);
|
115 |
+
|
116 |
+
if (!$success) {
|
117 |
+
$result[count($result) - 1] .= '. FAILED';
|
118 |
+
$result = array_merge($result, $errors);
|
119 |
+
$result[] = 'The test cannot be completed';
|
120 |
+
//$result[count($result) - 1] .= '. FAILED';
|
121 |
+
return [false, $result, $createdTestFiles];
|
122 |
+
}
|
123 |
+
//$result[count($result) - 1] .= '. ok!';
|
124 |
+
$result[] = '*' . $requestUrl . '*';
|
125 |
+
|
126 |
+
$result = array_merge($result, SelfTestHelper::printHeaders($headers));
|
127 |
+
|
128 |
+
if (!isset($headers['content-type'])) {
|
129 |
+
$result[] = 'Bummer. There is no "content-type" response header. The test FAILED';
|
130 |
+
return [false, $result, $createdTestFiles];
|
131 |
+
}
|
132 |
+
|
133 |
+
if ($headers['content-type'] == 'image/webp') {
|
134 |
+
$result[] = '**Bummer**{: .error}. As the "content-type" header reveals, we got the webp. ' .
|
135 |
+
'So even browsers not supporting webp gets webp. Not good!';
|
136 |
+
$result[] = 'The test FAILED.';
|
137 |
+
|
138 |
+
$result[] = '### What to do now?';
|
139 |
+
// TODO: We could examine the headers for common CDN responses
|
140 |
+
|
141 |
+
$result[] = 'First, examine the response headers above. Is there any indication that ' .
|
142 |
+
'the image is returned from a CDN cache? ' .
|
143 |
+
$result[] = 'If there is: Check out the ' .
|
144 |
+
'*How do I configure my CDN in “Varied image responses” operation mode?* section in the FAQ ' .
|
145 |
+
'(https://wordpress.org/plugins/webp-express/)';
|
146 |
+
|
147 |
+
if (PlatformInfo::isApache()) {
|
148 |
+
$result[] = 'If not: please report this in the forum, as it seems the .htaccess rules ';
|
149 |
+
$result[] = 'just arent working on your system.';
|
150 |
+
} elseif (PlatformInfo::isNginx()) {
|
151 |
+
$result[] = 'Also, as you are on Nginx, check out the ' .
|
152 |
+
' "I am on Nginx" section in the FAQ (https://wordpress.org/plugins/webp-express/)';
|
153 |
+
} else {
|
154 |
+
$result[] = 'If not: please report this in the forum, as it seems that there is something ' .
|
155 |
+
'in the *.htaccess* rules generated by WebP Express that are not working.';
|
156 |
+
}
|
157 |
+
|
158 |
+
$result[] = '### System info (for manual diagnosing):';
|
159 |
+
$result = array_merge($result, SelfTestHelper::allInfo($this->config));
|
160 |
+
|
161 |
+
|
162 |
+
return [false, $result, $createdTestFiles];
|
163 |
+
}
|
164 |
+
|
165 |
+
if ($headers['content-type'] != 'image/' . $imageType) {
|
166 |
+
$result[] = 'Bummer. As the "content-type" header reveals, we did not get the ' . $imageType .
|
167 |
+
'Surprisingly we got: "' . $headers['content-type'] . '"';
|
168 |
+
$result[] = 'The test FAILED.';
|
169 |
+
return [false, $result, $createdTestFiles];
|
170 |
+
}
|
171 |
+
$result[] = 'Alrighty. We got the ' . $imageType . '. **Great!**{: .ok}.';
|
172 |
+
|
173 |
+
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
174 |
+
$result[count($result) - 1] .= '. **BUT!**';
|
175 |
+
$result[] = '**We did not receive a Vary:Accept header. ' .
|
176 |
+
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
177 |
+
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
178 |
+
$noWarningsYet = false;
|
179 |
+
}
|
180 |
+
|
181 |
+
return [$noWarningsYet, $result, $createdTestFiles];
|
182 |
+
}
|
183 |
+
|
184 |
+
protected function getSuccessMessage()
|
185 |
+
{
|
186 |
+
return 'Everything **seems to work**{: .ok} as it should. ' .
|
187 |
+
'However, a check is on the TODO: ' .
|
188 |
+
'TODO: Check that disabled image types does not get converted. ';
|
189 |
+
}
|
190 |
+
|
191 |
+
public function startupTests()
|
192 |
+
{
|
193 |
+
$result[] = '# Testing redirection to converter';
|
194 |
+
if (!$this->config['enable-redirection-to-converter']) {
|
195 |
+
$result[] = 'Turned off, nothing to test (if you just turned it on without saving, remember: this is a live test so you need to save settings)';
|
196 |
+
return [false, $result];
|
197 |
+
}
|
198 |
+
return [true, $result];
|
199 |
+
}
|
200 |
+
|
201 |
+
public static function runTest()
|
202 |
+
{
|
203 |
+
$config = Config::loadConfigAndFix(false);
|
204 |
+
$me = new SelfTestRedirectToConverter($config);
|
205 |
+
return $me->startTest();
|
206 |
+
}
|
207 |
+
|
208 |
+
}
|
lib/classes/SelfTestRedirectToExisting.php
ADDED
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
class SelfTestRedirectToExisting extends SelfTestRedirectAbstract
|
6 |
+
{
|
7 |
+
/**
|
8 |
+
* Run test for either jpeg or png
|
9 |
+
*
|
10 |
+
* @param string $rootId (ie "uploads" or "themes")
|
11 |
+
* @param string $imageType ("jpeg" or "png")
|
12 |
+
* @return array [$success, $result, $createdTestFiles]
|
13 |
+
*/
|
14 |
+
protected function runTestForImageType($rootId, $imageType)
|
15 |
+
{
|
16 |
+
$result = [];
|
17 |
+
$createdTestFiles = false;
|
18 |
+
$noWarningsYet = true;
|
19 |
+
|
20 |
+
$result[] = '### Copying files for testing';
|
21 |
+
|
22 |
+
// Copy test image
|
23 |
+
list($subResult, $success, $sourceFileName) = SelfTestHelper::copyTestImageToRoot($rootId, $imageType);
|
24 |
+
$result = array_merge($result, $subResult);
|
25 |
+
if (!$success) {
|
26 |
+
$result[] = 'The test cannot be completed';
|
27 |
+
return [false, $result, $createdTestFiles];
|
28 |
+
}
|
29 |
+
$createdTestFiles = true;
|
30 |
+
|
31 |
+
$result[] = '';
|
32 |
+
|
33 |
+
// Copy dummy webp
|
34 |
+
list($subResult, $success, $destinationFile) = SelfTestHelper::copyDummyWebPToCacheFolder(
|
35 |
+
$rootId,
|
36 |
+
$this->config['destination-folder'],
|
37 |
+
$this->config['destination-extension'],
|
38 |
+
$this->config['destination-structure'],
|
39 |
+
$sourceFileName,
|
40 |
+
$imageType
|
41 |
+
);
|
42 |
+
$result = array_merge($result, $subResult);
|
43 |
+
if (!$success) {
|
44 |
+
$result[] = 'The test cannot be completed';
|
45 |
+
return [false, $result, $createdTestFiles];
|
46 |
+
}
|
47 |
+
|
48 |
+
$requestUrl = Paths::getUrlById($rootId) . '/webp-express-test-images/' . $sourceFileName;
|
49 |
+
$result[] = '### Lets check that browsers supporting webp gets the WEBP when the ' . strtoupper($imageType) . ' is requested';
|
50 |
+
$result[] = 'Making a HTTP request for the test image (pretending to be a client that supports webp, by setting the "Accept" header to "image/webp")';
|
51 |
+
$requestArgs = [
|
52 |
+
'headers' => [
|
53 |
+
'ACCEPT' => 'image/webp'
|
54 |
+
]
|
55 |
+
];
|
56 |
+
list($success, $errors, $headers, $return) = SelfTestHelper::remoteGet($requestUrl, $requestArgs);
|
57 |
+
|
58 |
+
if (!$success) {
|
59 |
+
$result[count($result) - 1] .= '. FAILED';
|
60 |
+
$result = array_merge($result, $errors);
|
61 |
+
$result[] = 'The test cannot be completed';
|
62 |
+
//$result[count($result) - 1] .= '. FAILED';
|
63 |
+
return [false, $result, $createdTestFiles];
|
64 |
+
}
|
65 |
+
//$result[count($result) - 1] .= '. ok!';
|
66 |
+
$result[] = '*' . $requestUrl . '*';
|
67 |
+
|
68 |
+
$result = array_merge($result, SelfTestHelper::printHeaders($headers));
|
69 |
+
|
70 |
+
if (!isset($headers['content-type'])) {
|
71 |
+
$result[] = 'Bummer. There is no "content-type" response header. The test FAILED';
|
72 |
+
return [false, $result, $createdTestFiles];
|
73 |
+
}
|
74 |
+
|
75 |
+
if ($headers['content-type'] != 'image/webp') {
|
76 |
+
|
77 |
+
if ($headers['content-type'] == 'image/' . $imageType) {
|
78 |
+
$result[] = 'Bummer. As the "content-type" header reveals, we got the ' . $imageType . '. ';
|
79 |
+
} else {
|
80 |
+
$result[] = 'Bummer. As the "content-type" header reveals, we did not get a webp' .
|
81 |
+
'Surprisingly we got: "' . $headers['content-type'] . '"';
|
82 |
+
if (strpos($headers['content-type'], 'text/html') !== false) {
|
83 |
+
$result[] = 'Body:';
|
84 |
+
$result[] = print_r($return['body'], true);
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
if (isset($headers['content-length'])) {
|
89 |
+
if ($headers['content-length'] == '6964') {
|
90 |
+
$result[] = 'However, the content-length reveals that we actually GOT the webp ' .
|
91 |
+
'(we know that the file we put is exactly 6964 bytes). ' .
|
92 |
+
'So it is "just" the content-type header that was not set correctly.';
|
93 |
+
|
94 |
+
if (PlatformInfo::isNginx()) {
|
95 |
+
$result[] = 'As you are on Nginx, you probably need to add the following line ' .
|
96 |
+
'in your *mime.types* configuration file: ';
|
97 |
+
$result[] = '```image/webp webp;```';
|
98 |
+
} else {
|
99 |
+
$result[] = 'Perhaps you dont have *mod_mime* installed, or the following lines are not in a *.htaccess* ' .
|
100 |
+
'in the folder containing the webp (or a parent):';
|
101 |
+
$result[] = "```<IfModule mod_mime.c>\n AddType image/webp .webp\n</IfModule>```";
|
102 |
+
|
103 |
+
$result[] = '### .htaccess status';
|
104 |
+
$result = array_merge($result, SelfTestHelper::htaccessInfo($this->config, true));
|
105 |
+
}
|
106 |
+
|
107 |
+
$result[] = 'The test **FAILED**{: .error}.';
|
108 |
+
} else {
|
109 |
+
$result[] = 'Additionally, the content-length reveals that we did not get the webp ' .
|
110 |
+
'(we know that the file we put is exactly 6964 bytes). ' .
|
111 |
+
'So we can conclude that the rewrite did not happen';
|
112 |
+
$result[] = 'The test **FAILED**{: .error}.';
|
113 |
+
$result[] = '#### Diagnosing rewrites';
|
114 |
+
$result = array_merge($result, SelfTestHelper::diagnoseFailedRewrite($this->config));
|
115 |
+
}
|
116 |
+
} else {
|
117 |
+
$result[] = 'In addition, we did not get a *content-length* header either.' .
|
118 |
+
$result[] = 'It seems we can conclude that the rewrite did not happen.';
|
119 |
+
$result[] = 'The test **FAILED**{: .error}.';
|
120 |
+
$result[] = '#### Diagnosing rewrites';
|
121 |
+
$result = array_merge($result, SelfTestHelper::diagnoseFailedRewrite($this->config));
|
122 |
+
}
|
123 |
+
|
124 |
+
return [false, $result, $createdTestFiles];
|
125 |
+
}
|
126 |
+
|
127 |
+
if (isset($headers['x-webp-convert-log'])) {
|
128 |
+
$result[] = 'Bummer. Although we did get a webp, we did not get it as a result of a direct ' .
|
129 |
+
'redirection. This webp was returned by the PHP script. Although this works, it takes more ' .
|
130 |
+
'resources to ignite the PHP engine for each image request than redirecting directly to the image.';
|
131 |
+
$result[] = 'The test FAILED.';
|
132 |
+
|
133 |
+
$result[] = 'It seems something went wrong with the redirection.';
|
134 |
+
$result[] = '#### Diagnosing redirects';
|
135 |
+
$result = array_merge($result, SelfTestHelper::diagnoseFailedRewrite($this->config));
|
136 |
+
|
137 |
+
return [false, $result, $createdTestFiles];
|
138 |
+
} else {
|
139 |
+
$result[] = 'Alrighty. We got a webp. Just what we wanted. **Great!**{: .ok}';
|
140 |
+
}
|
141 |
+
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
142 |
+
$result[count($result) - 1] .= '. **BUT!**';
|
143 |
+
$result[] = '**Warning: We did not receive a Vary:Accept header. ' .
|
144 |
+
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
145 |
+
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
146 |
+
$noWarningsYet = false;
|
147 |
+
}
|
148 |
+
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
149 |
+
$result[] = '**Notice: No cache-control or expires header has been set. ' .
|
150 |
+
'It is recommended to do so. Set it nice and big once you are sure the webps have a good quality/compression compromise.**{: .warn}';
|
151 |
+
}
|
152 |
+
$result[] = '';
|
153 |
+
|
154 |
+
|
155 |
+
// Check browsers NOT supporting webp
|
156 |
+
// -----------------------------------
|
157 |
+
$result[] = '### Now lets check that browsers *not* supporting webp gets the ' . strtoupper($imageType);
|
158 |
+
$result[] = 'Making a HTTP request for the test image (without setting the "Accept" header)';
|
159 |
+
list($success, $errors, $headers) = SelfTestHelper::remoteGet($requestUrl);
|
160 |
+
|
161 |
+
if (!$success) {
|
162 |
+
$result[count($result) - 1] .= '. FAILED';
|
163 |
+
$result = array_merge($result, $errors);
|
164 |
+
$result[] = 'The test cannot be completed';
|
165 |
+
//$result[count($result) - 1] .= '. FAILED';
|
166 |
+
return [false, $result, $createdTestFiles];
|
167 |
+
}
|
168 |
+
//$result[count($result) - 1] .= '. ok!';
|
169 |
+
$result[] = '*' . $requestUrl . '*';
|
170 |
+
|
171 |
+
$result = array_merge($result, SelfTestHelper::printHeaders($headers));
|
172 |
+
|
173 |
+
if (!isset($headers['content-type'])) {
|
174 |
+
$result[] = 'Bummer. There is no "content-type" response header. The test FAILED';
|
175 |
+
return [false, $result, $createdTestFiles];
|
176 |
+
}
|
177 |
+
|
178 |
+
if ($headers['content-type'] == 'image/webp') {
|
179 |
+
$result[] = '**Bummer**{: .error}. As the "content-type" header reveals, we got the webp. ' .
|
180 |
+
'So even browsers not supporting webp gets webp. Not good!';
|
181 |
+
$result[] = 'The test FAILED.';
|
182 |
+
|
183 |
+
$result[] = '#### What to do now?';
|
184 |
+
// TODO: We could examine the headers for common CDN responses
|
185 |
+
|
186 |
+
$result[] = 'First, examine the response headers above. Is there any indication that ' .
|
187 |
+
'the image is returned from a CDN cache? ' .
|
188 |
+
$result[] = 'If there is: Check out the ' .
|
189 |
+
'*How do I configure my CDN in “Varied image responses” operation mode?* section in the FAQ ' .
|
190 |
+
'(https://wordpress.org/plugins/webp-express/)';
|
191 |
+
|
192 |
+
if (PlatformInfo::isApache()) {
|
193 |
+
$result[] = 'If not: please report this in the forum, as it seems the .htaccess rules ';
|
194 |
+
$result[] = 'just arent working on your system.';
|
195 |
+
} elseif (PlatformInfo::isNginx()) {
|
196 |
+
$result[] = 'Also, as you are on Nginx, check out the ' .
|
197 |
+
' "I am on Nginx" section in the FAQ (https://wordpress.org/plugins/webp-express/)';
|
198 |
+
} else {
|
199 |
+
$result[] = 'If not: please report this in the forum, as it seems that there is something ' .
|
200 |
+
'in the *.htaccess* rules generated by WebP Express that are not working.';
|
201 |
+
}
|
202 |
+
|
203 |
+
$result[] = '### System info (for manual diagnosing):';
|
204 |
+
$result = array_merge($result, SelfTestHelper::allInfo($this->config));
|
205 |
+
|
206 |
+
|
207 |
+
return [false, $result, $createdTestFiles];
|
208 |
+
}
|
209 |
+
|
210 |
+
if ($headers['content-type'] != 'image/' . $imageType) {
|
211 |
+
$result[] = 'Bummer. As the "content-type" header reveals, we did not get the ' . $imageType .
|
212 |
+
'Surprisingly we got: "' . $headers['content-type'] . '"';
|
213 |
+
$result[] = 'The test FAILED.';
|
214 |
+
return [false, $result, $createdTestFiles];
|
215 |
+
}
|
216 |
+
$result[] = 'Alrighty. We got the ' . $imageType . '. **Great!**{: .ok}.';
|
217 |
+
|
218 |
+
if (!SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
219 |
+
$result[count($result) - 1] .= '. **BUT!**';
|
220 |
+
$result[] = '**We did not receive a Vary:Accept header. ' .
|
221 |
+
'That header should be set in order to tell proxies that the response varies depending on the ' .
|
222 |
+
'Accept header. Otherwise browsers not supporting webp might get a cached webp and vice versa.**{: .warn}';
|
223 |
+
$noWarningsYet = false;
|
224 |
+
}
|
225 |
+
|
226 |
+
return [$noWarningsYet, $result, $createdTestFiles];
|
227 |
+
}
|
228 |
+
|
229 |
+
protected function getSuccessMessage()
|
230 |
+
{
|
231 |
+
return 'Everything **seems to work**{: .ok} as it should. ' .
|
232 |
+
'However, a couple of things were not tested (it is on the TODO). ' .
|
233 |
+
'TODO 1: If one image type is disabled, check that it does not redirect to webp (unless redirection to converter is set up). ' .
|
234 |
+
'TODO 2: Test that redirection to webp only is triggered when the webp exists. ';
|
235 |
+
}
|
236 |
+
|
237 |
+
public function startupTests()
|
238 |
+
{
|
239 |
+
$result[] = '# Testing redirection to existing webp';
|
240 |
+
if (!$this->config['redirect-to-existing-in-htaccess']) {
|
241 |
+
$result[] = 'Turned off, nothing to test (if you just turned it on without saving, remember: this is a live test so you need to save settings)';
|
242 |
+
return [false, $result];
|
243 |
+
}
|
244 |
+
return [true, $result];
|
245 |
+
}
|
246 |
+
|
247 |
+
public static function runTest()
|
248 |
+
{
|
249 |
+
$config = Config::loadConfigAndFix(false);
|
250 |
+
$me = new SelfTestRedirectToExisting($config);
|
251 |
+
return $me->startTest();
|
252 |
+
}
|
253 |
+
}
|
lib/classes/SelfTestRedirectToWebPRealizer.php
ADDED
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
class SelfTestRedirectToWebPRealizer extends SelfTestRedirectAbstract
|
6 |
+
{
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Run test for either jpeg or png
|
10 |
+
*
|
11 |
+
* @param string $rootId (ie "uploads" or "themes")
|
12 |
+
* @param string $imageType ("jpeg" or "png")
|
13 |
+
* @return array [$success, $result, $createdTestFiles]
|
14 |
+
*/
|
15 |
+
protected function runTestForImageType($rootId, $imageType)
|
16 |
+
{
|
17 |
+
$result = [];
|
18 |
+
$createdTestFiles = false;
|
19 |
+
$noWarningsYet = true;
|
20 |
+
|
21 |
+
// Copy test image
|
22 |
+
list($subResult, $success, $sourceFileName) = SelfTestHelper::copyTestImageToRoot($rootId, $imageType);
|
23 |
+
$result = array_merge($result, $subResult);
|
24 |
+
if (!$success) {
|
25 |
+
$result[] = 'The test cannot be completed';
|
26 |
+
return [false, $result, $createdTestFiles];
|
27 |
+
}
|
28 |
+
$createdTestFiles = true;
|
29 |
+
|
30 |
+
//$requestUrl = Paths::getUploadUrl() . '/' . $sourceFileName;
|
31 |
+
|
32 |
+
// Hacky, I know.
|
33 |
+
// AlterHtmlHelper was not meant to be used like this, but it is the only place where we currently
|
34 |
+
// have logic for finding destination url from source url.
|
35 |
+
|
36 |
+
//$sourceUrl = Paths::getUploadUrl() . '/' . $sourceFileName;
|
37 |
+
$sourceUrl = Paths::getUrlById($rootId) . '/webp-express-test-images/' . $sourceFileName;
|
38 |
+
|
39 |
+
AlterHtmlHelper::$options = json_decode(Option::getOption('webp-express-alter-html-options', null), true);
|
40 |
+
AlterHtmlHelper::$options['only-for-webps-that-exists'] = false;
|
41 |
+
|
42 |
+
$requestUrl = AlterHtmlHelper::getWebPUrlInBase(
|
43 |
+
$sourceUrl,
|
44 |
+
$rootId,
|
45 |
+
Paths::getUrlById($rootId),
|
46 |
+
Paths::getAbsDirById($rootId)
|
47 |
+
);
|
48 |
+
|
49 |
+
|
50 |
+
$result[] = '### Lets check that browsers supporting webp gets a freshly converted WEBP ' .
|
51 |
+
'when a non-existing WEBP is requested, which has a corresponding source';
|
52 |
+
$result[] = 'Making a HTTP request for the test image (pretending to be a client that supports webp, by setting the "Accept" header to "image/webp")';
|
53 |
+
$requestArgs = [
|
54 |
+
'headers' => [
|
55 |
+
'ACCEPT' => 'image/webp'
|
56 |
+
]
|
57 |
+
];
|
58 |
+
list($success, $errors, $headers, $return) = SelfTestHelper::remoteGet($requestUrl, $requestArgs);
|
59 |
+
|
60 |
+
if (!$success) {
|
61 |
+
//$result[count($result) - 1] .= '. FAILED';
|
62 |
+
//$result[] = '*' . $requestUrl . '*';
|
63 |
+
|
64 |
+
$result = array_merge($result, $errors);
|
65 |
+
$result[] = 'The test **failed**{: .error}';
|
66 |
+
|
67 |
+
$result[] = 'Why did it fail? It could either be that the redirection rule did not trigger ' .
|
68 |
+
'or it could be that the PHP script could not locate a source image corresponding to the destination URL. ' .
|
69 |
+
'Currently, this analysis cannot dertermine which was the case and it cannot be helpful ' .
|
70 |
+
'if the latter is the case (sorry!). However, if the redirection rules are the problem, here is some info:';
|
71 |
+
|
72 |
+
$result[] = '### Diagnosing redirection problems (presuming it is the redirection to the script that is failing)';
|
73 |
+
$result = array_merge($result, SelfTestHelper::diagnoseFailedRewrite($this->config));
|
74 |
+
|
75 |
+
|
76 |
+
//$result[count($result) - 1] .= '. FAILED';
|
77 |
+
return [false, $result, $createdTestFiles];
|
78 |
+
}
|
79 |
+
//$result[count($result) - 1] .= '. ok!';
|
80 |
+
$result[] = '*' . $requestUrl . '*';
|
81 |
+
|
82 |
+
$result = array_merge($result, SelfTestHelper::printHeaders($headers));
|
83 |
+
|
84 |
+
if (!isset($headers['content-type'])) {
|
85 |
+
$result[] = 'Bummer. There is no "content-type" response header. The test FAILED';
|
86 |
+
return [false, $result, $createdTestFiles];
|
87 |
+
}
|
88 |
+
|
89 |
+
if ($headers['content-type'] == 'image/' . $imageType) {
|
90 |
+
$result[] = 'Bummer. As the "content-type" header reveals, we got the ' . $imageType . '.';
|
91 |
+
$result[] = 'The test **failed**{: .error}.';
|
92 |
+
$result[] = 'Now, what went wrong?';
|
93 |
+
|
94 |
+
if (isset($headers['x-webp-convert-log'])) {
|
95 |
+
//$result[] = 'Inspect the "x-webp-convert-log" headers above, and you ' .
|
96 |
+
// 'should have your answer (it is probably because you do not have any conversion methods working).';
|
97 |
+
if (SelfTestHelper::hasHeaderContaining($headers, 'x-webp-convert-log', 'Performing fail action: original')) {
|
98 |
+
$result[] = 'The answer lies in the "x-convert-log" response headers: ' .
|
99 |
+
'**The conversion failed**{: .error}. ';
|
100 |
+
}
|
101 |
+
} else {
|
102 |
+
$result[] = 'Well, there is indication that the redirection isnt working. ' .
|
103 |
+
'The PHP script should set "x-webp-convert-log" response headers, but there are none. ';
|
104 |
+
'While these headers could have been eaten in a Cloudflare-like setup, the problem is ';
|
105 |
+
'probably that the redirection simply failed';
|
106 |
+
|
107 |
+
$result[] = '### Diagnosing redirection problems';
|
108 |
+
$result = array_merge($result, SelfTestHelper::diagnoseFailedRewrite($this->config));
|
109 |
+
}
|
110 |
+
return [false, $result, $createdTestFiles];
|
111 |
+
}
|
112 |
+
|
113 |
+
if ($headers['content-type'] != 'image/webp') {
|
114 |
+
$result[] = 'However. As the "content-type" header reveals, we did not get a webp' .
|
115 |
+
'Surprisingly we got: "' . $headers['content-type'] . '"';
|
116 |
+
$result[] = 'The test FAILED.';
|
117 |
+
if (strpos($headers['content-type'], 'text/html') !== false) {
|
118 |
+
$result[] = 'Body:';
|
119 |
+
$result[] = print_r($return['body'], true);
|
120 |
+
}
|
121 |
+
return [false, $result, $createdTestFiles];
|
122 |
+
}
|
123 |
+
|
124 |
+
$result[] = '**Alrighty**{: .ok}. We got a webp.';
|
125 |
+
if (isset($headers['x-webp-convert-log'])) {
|
126 |
+
$result[] = 'The "x-webp-convert-log" headers reveals we got the webp from the PHP script. **Great!**{: .ok}';
|
127 |
+
} else {
|
128 |
+
$result[] = 'Interestingly, there are no "x-webp-convert-log" headers even though ' .
|
129 |
+
'the PHP script always produces such. Could it be you have some weird setup that eats these headers?';
|
130 |
+
}
|
131 |
+
|
132 |
+
if (SelfTestHelper::hasVaryAcceptHeader($headers)) {
|
133 |
+
$result[] = 'All is however not super-duper:';
|
134 |
+
|
135 |
+
$result[] = '**Notice: We received a Vary:Accept header. ' .
|
136 |
+
'That header need not to be set. Actually, it is a little bit bad for performance ' .
|
137 |
+
'as proxies are currently doing a bad job maintaining several caches (in many cases they simply do not)**{: .warn}';
|
138 |
+
$noWarningsYet = false;
|
139 |
+
}
|
140 |
+
if (!SelfTestHelper::hasCacheControlOrExpiresHeader($headers)) {
|
141 |
+
$result[] = '**Notice: No cache-control or expires header has been set. ' .
|
142 |
+
'It is recommended to do so. Set it nice and big once you are sure the webps have a good quality/compression compromise.**{: .warn}';
|
143 |
+
}
|
144 |
+
$result[] = '';
|
145 |
+
|
146 |
+
return [$noWarningsYet, $result, $createdTestFiles];
|
147 |
+
}
|
148 |
+
|
149 |
+
/*
|
150 |
+
private static function doRunTest($this->config)
|
151 |
+
{
|
152 |
+
$result = [];
|
153 |
+
$result[] = '# Testing redirection to converter';
|
154 |
+
|
155 |
+
$createdTestFiles = false;
|
156 |
+
if (!file_exists(Paths::getConfigFileName())) {
|
157 |
+
$result[] = 'Hold on. You need to save options before you can run this test. There is no config file yet.';
|
158 |
+
return [true, $result, $createdTestFiles];
|
159 |
+
}
|
160 |
+
|
161 |
+
|
162 |
+
if ($this->config['image-types'] == 0) {
|
163 |
+
$result[] = 'No image types have been activated, nothing to test';
|
164 |
+
return [true, $result, $createdTestFiles];
|
165 |
+
}
|
166 |
+
|
167 |
+
if ($this->config['image-types'] & 1) {
|
168 |
+
list($success, $subResult, $createdTestFiles) = self::runTestForImageType($this->config, 'jpeg');
|
169 |
+
$result = array_merge($result, $subResult);
|
170 |
+
|
171 |
+
if ($success) {
|
172 |
+
if ($this->config['image-types'] & 2) {
|
173 |
+
$result[] = '### Performing same tests for PNG';
|
174 |
+
list($success, $subResult, $createdTestFiles2) = self::runTestForImageType($this->config, 'png');
|
175 |
+
$createdTestFiles = $createdTestFiles || $createdTestFiles2;
|
176 |
+
if ($success) {
|
177 |
+
//$result[count($result) - 1] .= '. **ok**{: .ok}';
|
178 |
+
$result[] .= 'All tests passed for PNG as well.';
|
179 |
+
$result[] = '(I shall spare you for the report, which is almost identical to the one above)';
|
180 |
+
} else {
|
181 |
+
$result = array_merge($result, $subResult);
|
182 |
+
}
|
183 |
+
}
|
184 |
+
}
|
185 |
+
} else {
|
186 |
+
list($success, $subResult, $createdTestFiles) = self::runTestForImageType($this->config, 'png');
|
187 |
+
$result = array_merge($result, $subResult);
|
188 |
+
}
|
189 |
+
|
190 |
+
if ($success) {
|
191 |
+
$result[] = '### Conclusion';
|
192 |
+
$result[] = 'Everything **seems to work**{: .ok} as it should. ' .
|
193 |
+
'However, notice that this test only tested an image which was placed in the *uploads* folder. ' .
|
194 |
+
'The rest of the image roots (such as theme images) have not been tested (it is on the TODO). ' .
|
195 |
+
'Also on the TODO: If one image type is disabled, check that it does not redirect to the conversion script. ' .
|
196 |
+
'These things probably work, though.';
|
197 |
+
}
|
198 |
+
|
199 |
+
|
200 |
+
return [true, $result, $createdTestFiles];
|
201 |
+
}*/
|
202 |
+
|
203 |
+
protected function getSuccessMessage()
|
204 |
+
{
|
205 |
+
return 'Everything **seems to work**{: .ok} as it should. ' .
|
206 |
+
'However, a check is on the TODO: ' .
|
207 |
+
'TODO: Check that disabled image types does not get converted. ';
|
208 |
+
}
|
209 |
+
|
210 |
+
public function startupTests()
|
211 |
+
{
|
212 |
+
$result[] = '# Testing "WebP Realizer" functionality';
|
213 |
+
if (!$this->config['enable-redirection-to-webp-realizer']) {
|
214 |
+
$result[] = 'Turned off, nothing to test (if you just turned it on without saving, remember: this is a live test so you need to save settings)';
|
215 |
+
return [false, $result];
|
216 |
+
}
|
217 |
+
return [true, $result];
|
218 |
+
}
|
219 |
+
|
220 |
+
public static function runTest()
|
221 |
+
{
|
222 |
+
$config = Config::loadConfigAndFix(false);
|
223 |
+
$me = new SelfTestRedirectToWebPRealizer($config);
|
224 |
+
return $me->startTest();
|
225 |
+
}
|
226 |
+
|
227 |
+
|
228 |
+
}
|
lib/classes/WebPOnDemand.php
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
This class is used by wod/webp-on-demand.php, which does not do a Wordpress bootstrap, but does register an autoloader for
|
4 |
+
the WebPExpress classes.
|
5 |
+
|
6 |
+
Calling Wordpress functions will FAIL. Make sure not to do that in either this class or the helpers.
|
7 |
+
*/
|
8 |
+
//error_reporting(E_ALL);
|
9 |
+
//ini_set('display_errors', 1);
|
10 |
+
|
11 |
+
namespace WebPExpress;
|
12 |
+
|
13 |
+
use \WebPExpress\ConvertHelperIndependent;
|
14 |
+
use \WebPExpress\Sanitize;
|
15 |
+
use \WebPExpress\SanityCheck;
|
16 |
+
use \WebPExpress\SanityException;
|
17 |
+
use \WebPExpress\ValidateException;
|
18 |
+
use \WebPExpress\Validate;
|
19 |
+
use \WebPExpress\WodConfigLoader;
|
20 |
+
|
21 |
+
class WebPOnDemand extends WodConfigLoader
|
22 |
+
{
|
23 |
+
private static function getSourceDocRoot() {
|
24 |
+
|
25 |
+
|
26 |
+
//echo 't:' . $_GET['test'];exit;
|
27 |
+
// Check if it is in an environment variable
|
28 |
+
$source = self::getEnvPassedInRewriteRule('REQFN');
|
29 |
+
if ($source !== false) {
|
30 |
+
self::$checking = 'source (passed through env)';
|
31 |
+
return SanityCheck::absPathExistsAndIsFile($source);
|
32 |
+
}
|
33 |
+
|
34 |
+
// Check if it is in header (but only if .htaccess was configured to send in header)
|
35 |
+
if (isset($wodOptions['base-htaccess-on-these-capability-tests'])) {
|
36 |
+
$capTests = $wodOptions['base-htaccess-on-these-capability-tests'];
|
37 |
+
$passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
|
38 |
+
$passThrougEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
|
39 |
+
// This determines if .htaccess was configured to send in querystring
|
40 |
+
$headerMagicAddedInHtaccess = ((!$passThrougEnvVarDefinitelyAvailable) && (!$passThroughHeaderDefinitelyUnavailable));
|
41 |
+
} else {
|
42 |
+
$headerMagicAddedInHtaccess = true; // pretend its true
|
43 |
+
}
|
44 |
+
if ($headerMagicAddedInHtaccess && (isset($_SERVER['HTTP_REQFN']))) {
|
45 |
+
self::$checking = 'source (passed through request header)';
|
46 |
+
return SanityCheck::absPathExistsAndIsFile($_SERVER['HTTP_REQFN']);
|
47 |
+
}
|
48 |
+
|
49 |
+
if (!isset(self::$docRoot)) {
|
50 |
+
//$source = self::getEnvPassedInRewriteRule('REQFN');
|
51 |
+
if (isset($_GET['root-id']) && isset($_GET['xsource-rel-to-root-id'])) {
|
52 |
+
$xsrcRelToRootId = SanityCheck::noControlChars($_GET['xsource-rel-to-root-id']);
|
53 |
+
$srcRelToRootId = SanityCheck::pathWithoutDirectoryTraversal(substr($xsrcRelToRootId, 1));
|
54 |
+
//echo $srcRelToRootId; exit;
|
55 |
+
|
56 |
+
$rootId = SanityCheck::noControlChars($_GET['root-id']);
|
57 |
+
SanityCheck::pregMatch('#^[a-z]+$#', $rootId, 'Not a valid root-id');
|
58 |
+
|
59 |
+
$source = self::getRootPathById($rootId) . '/' . $srcRelToRootId;
|
60 |
+
return SanityCheck::absPathExistsAndIsFile($source);
|
61 |
+
}
|
62 |
+
}
|
63 |
+
|
64 |
+
// Check querystring (relative path to docRoot) - when docRoot is available
|
65 |
+
if (isset(self::$docRoot) && isset($_GET['xsource-rel'])) {
|
66 |
+
self::$checking = 'source (passed as relative path, through querystring)';
|
67 |
+
$xsrcRel = SanityCheck::noControlChars($_GET['xsource-rel']);
|
68 |
+
$srcRel = SanityCheck::pathWithoutDirectoryTraversal(substr($xsrcRel, 1));
|
69 |
+
return SanityCheck::absPathExistsAndIsFile(self::$docRoot . '/' . $srcRel);
|
70 |
+
}
|
71 |
+
|
72 |
+
// Check querystring (relative path to plugin) - when docRoot is unavailable
|
73 |
+
/*TODO
|
74 |
+
if (!isset(self::$docRoot) && isset($_GET['xsource-rel-to-plugin-dir'])) {
|
75 |
+
self::$checking = 'source (passed as relative path to plugin dir, through querystring)';
|
76 |
+
$xsrcRelPlugin = SanityCheck::noControlChars($_GET['xsource-rel-to-plugin-dir']);
|
77 |
+
$srcRelPlugin = SanityCheck::pathWithoutDirectoryTraversal(substr($xsrcRelPlugin, 1));
|
78 |
+
return SanityCheck::absPathExistsAndIsFile(self::$docRoot . '/' . $srcRel);
|
79 |
+
}*/
|
80 |
+
|
81 |
+
|
82 |
+
// Check querystring (full path)
|
83 |
+
// - But only on Nginx (our Apache .htaccess rules never passes absolute url)
|
84 |
+
if (
|
85 |
+
(stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) &&
|
86 |
+
(isset($_GET['source']) || isset($_GET['xsource']))
|
87 |
+
) {
|
88 |
+
self::$checking = 'source (passed as absolute path on nginx)';
|
89 |
+
if (isset($_GET['source'])) {
|
90 |
+
$source = SanityCheck::absPathExistsAndIsFile($_GET['source']);
|
91 |
+
} else {
|
92 |
+
$xsrc = SanityCheck::noControlChars($_GET['xsource']);
|
93 |
+
return SanityCheck::absPathExistsAndIsFile(substr($xsrc, 1));
|
94 |
+
}
|
95 |
+
}
|
96 |
+
|
97 |
+
// Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
|
98 |
+
// correct result in all setups (ie "folder method 1")
|
99 |
+
if (isset(self::$docRoot)) {
|
100 |
+
self::$checking = 'source (retrieved by the request_uri server var)';
|
101 |
+
$srcRel = SanityCheck::pathWithoutDirectoryTraversal(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
|
102 |
+
return SanityCheck::absPathExistsAndIsFile(self::$docRoot . $srcRel);
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
private static function getSourceNoDocRoot()
|
107 |
+
{
|
108 |
+
$dirIdOfHtaccess = self::getEnvPassedInRewriteRule('WE_HTACCESS_ID');
|
109 |
+
if ($dirIdOfHtaccess === false) {
|
110 |
+
$dirIdOfHtaccess = SanityCheck::noControlChars($_GET['htaccess-id']);
|
111 |
+
}
|
112 |
+
|
113 |
+
if (!in_array($dirIdOfHtaccess, ['uploads', 'themes', 'wp-content', 'plugins', 'index'])) {
|
114 |
+
throw new \Exception('invalid htaccess directory id argument.');
|
115 |
+
}
|
116 |
+
|
117 |
+
// First try ENV
|
118 |
+
$sourceRelHtaccess = self::getEnvPassedInRewriteRule('WE_SOURCE_REL_HTACCESS');
|
119 |
+
|
120 |
+
// Otherwise use query-string
|
121 |
+
if ($sourceRelHtaccess === false) {
|
122 |
+
if (isset($_GET['xsource-rel-htaccess'])) {
|
123 |
+
$x = SanityCheck::noControlChars($_GET['xsource-rel-htaccess']);
|
124 |
+
$sourceRelHtaccess = SanityCheck::pathWithoutDirectoryTraversal(substr($x, 1));
|
125 |
+
} else {
|
126 |
+
throw new \Exception('Argument for source path is missing');
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
$sourceRelHtaccess = SanityCheck::pathWithoutDirectoryTraversal($sourceRelHtaccess);
|
131 |
+
|
132 |
+
|
133 |
+
$imageRoots = self::getImageRootsDef();
|
134 |
+
|
135 |
+
$source = $imageRoots->byId($dirIdOfHtaccess)->getAbsPath() . '/' . $sourceRelHtaccess;
|
136 |
+
return $source;
|
137 |
+
}
|
138 |
+
|
139 |
+
private static function getSource() {
|
140 |
+
if (self::$usingDocRoot) {
|
141 |
+
$source = self::getSourceDocRoot();
|
142 |
+
} else {
|
143 |
+
$source = self::getSourceNoDocRoot();
|
144 |
+
}
|
145 |
+
return $source;
|
146 |
+
}
|
147 |
+
|
148 |
+
private static function processRequestNoTryCatch() {
|
149 |
+
|
150 |
+
self::loadConfig();
|
151 |
+
|
152 |
+
$options = self::$options;
|
153 |
+
$wodOptions = self::$wodOptions;
|
154 |
+
$serveOptions = $options['webp-convert'];
|
155 |
+
$convertOptions = &$serveOptions['convert'];
|
156 |
+
//echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
|
157 |
+
|
158 |
+
|
159 |
+
// Validate that WebPExpress was configured to redirect to this conversion script
|
160 |
+
// (but do not require that for Nginx)
|
161 |
+
// ------------------------------------------------------------------------------
|
162 |
+
self::$checking = 'settings';
|
163 |
+
if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
164 |
+
if (!isset($wodOptions['enable-redirection-to-converter']) || ($wodOptions['enable-redirection-to-converter'] === false)) {
|
165 |
+
throw new ValidateException('Redirection to conversion script is not enabled');
|
166 |
+
}
|
167 |
+
}
|
168 |
+
|
169 |
+
// Check source (the image to be converted)
|
170 |
+
// --------------------------------------------
|
171 |
+
self::$checking = 'source';
|
172 |
+
$source = self::getSource();
|
173 |
+
|
174 |
+
//self::exitWithError($source);
|
175 |
+
|
176 |
+
$imageRoots = self::getImageRootsDef();
|
177 |
+
|
178 |
+
// Get upload dir
|
179 |
+
$uploadDirAbs = $imageRoots->byId('uploads')->getAbsPath();
|
180 |
+
|
181 |
+
// Check destination path
|
182 |
+
// --------------------------------------------
|
183 |
+
self::$checking = 'destination path';
|
184 |
+
$destination = ConvertHelperIndependent::getDestination(
|
185 |
+
$source,
|
186 |
+
$wodOptions['destination-folder'],
|
187 |
+
$wodOptions['destination-extension'],
|
188 |
+
self::$webExpressContentDirAbs,
|
189 |
+
$uploadDirAbs,
|
190 |
+
self::$usingDocRoot,
|
191 |
+
self::getImageRootsDef()
|
192 |
+
);
|
193 |
+
|
194 |
+
//$destination = SanityCheck::absPathIsInDocRoot($destination);
|
195 |
+
$destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Does not end with .webp');
|
196 |
+
|
197 |
+
//self::exitWithError($destination);
|
198 |
+
|
199 |
+
// Done with sanitizing, lets get to work!
|
200 |
+
// ---------------------------------------
|
201 |
+
if (isset($wodOptions['success-response']) && ($wodOptions['success-response'] == 'original')) {
|
202 |
+
$serveOptions['serve-original'] = true;
|
203 |
+
$serveOptions['serve-image']['headers']['vary-accept'] = false;
|
204 |
+
} else {
|
205 |
+
$serveOptions['serve-image']['headers']['vary-accept'] = true;
|
206 |
+
}
|
207 |
+
//echo $source . '<br>' . $destination; exit;
|
208 |
+
|
209 |
+
ConvertHelperIndependent::serveConverted(
|
210 |
+
$source,
|
211 |
+
$destination,
|
212 |
+
$serveOptions,
|
213 |
+
self::$webExpressContentDirAbs . '/log',
|
214 |
+
'Conversion triggered with the conversion script (wod/webp-on-demand.php)'
|
215 |
+
);
|
216 |
+
}
|
217 |
+
|
218 |
+
public static function processRequest() {
|
219 |
+
try {
|
220 |
+
self::processRequestNoTryCatch();
|
221 |
+
} catch (SanityException $e) {
|
222 |
+
self::exitWithError('Sanity check failed for ' . self::$checking . ': '. $e->getMessage());
|
223 |
+
} catch (ValidateException $e) {
|
224 |
+
self::exitWithError('Validation failed for ' . self::$checking . ': '. $e->getMessage());
|
225 |
+
} catch (\Exception $e) {
|
226 |
+
self::exitWithError('Error occured while calculating ' . self::$checking . ': '. $e->getMessage());
|
227 |
+
}
|
228 |
+
}
|
229 |
+
}
|
lib/classes/WebPRealizer.php
ADDED
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
This class is used by wod/webp-realizer.php, which does not do a Wordpress bootstrap, but does register an autoloader for
|
4 |
+
the WebPExpress classes.
|
5 |
+
|
6 |
+
Calling Wordpress functions will FAIL. Make sure not to do that in either this class or the helpers.
|
7 |
+
*/
|
8 |
+
//error_reporting(E_ALL);
|
9 |
+
//ini_set('display_errors', 1);
|
10 |
+
|
11 |
+
namespace WebPExpress;
|
12 |
+
|
13 |
+
use \WebPExpress\ConvertHelperIndependent;
|
14 |
+
use \WebPExpress\Sanitize;
|
15 |
+
use \WebPExpress\SanityCheck;
|
16 |
+
use \WebPExpress\SanityException;
|
17 |
+
use \WebPExpress\ValidateException;
|
18 |
+
use \WebPExpress\Validate;
|
19 |
+
use \WebPExpress\WodConfigLoader;
|
20 |
+
|
21 |
+
class WebPRealizer extends WodConfigLoader
|
22 |
+
{
|
23 |
+
private static function getDestinationDocRoot() {
|
24 |
+
$docRoot = self::$docRoot;
|
25 |
+
|
26 |
+
// Check if it is in an environment variable
|
27 |
+
$destRel = self::getEnvPassedInRewriteRule('DESTINATIONREL');
|
28 |
+
if ($destRel !== false) {
|
29 |
+
return SanityCheck::absPath($docRoot . '/' . $destRel);
|
30 |
+
}
|
31 |
+
|
32 |
+
// Check querystring (relative path)
|
33 |
+
if (isset($_GET['xdestination-rel'])) {
|
34 |
+
$xdestRel = SanityCheck::noControlChars($_GET['xdestination-rel']);
|
35 |
+
$destRel = SanityCheck::pathWithoutDirectoryTraversal(substr($xdestRel, 1));
|
36 |
+
$destination = SanityCheck::absPath($docRoot . '/' . $destRel);
|
37 |
+
return SanityCheck::absPathIsInDocRoot($destination);
|
38 |
+
}
|
39 |
+
|
40 |
+
// Check querystring (full path)
|
41 |
+
// - But only on Nginx (our Apache .htaccess rules never passes absolute url)
|
42 |
+
if (
|
43 |
+
(stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) &&
|
44 |
+
(isset($_GET['destination']) || isset($_GET['xdestination']))
|
45 |
+
) {
|
46 |
+
if (isset($_GET['destination'])) {
|
47 |
+
$destination = SanityCheck::absPathIsInDocRoot($_GET['destination']);
|
48 |
+
} else {
|
49 |
+
$xdest = SanityCheck::noControlChars($_GET['xdestination']);
|
50 |
+
$destination = SanityCheck::absPathIsInDocRoot(substr($xdest, 1));
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
// Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
|
55 |
+
// correct result in all setups (ie "folder method 1")
|
56 |
+
$destRel = SanityCheck::pathWithoutDirectoryTraversal(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
|
57 |
+
$destination = SanityCheck::absPath($docRoot . $destRel);
|
58 |
+
return SanityCheck::absPathIsInDocRoot($destination);
|
59 |
+
}
|
60 |
+
|
61 |
+
private static function getDestinationNoDocRoot() {
|
62 |
+
|
63 |
+
$dirIdOfHtaccess = self::getEnvPassedInRewriteRule('WE_HTACCESS_ID');
|
64 |
+
if ($dirIdOfHtaccess === false) {
|
65 |
+
$dirIdOfHtaccess = SanityCheck::noControlChars($_GET['htaccess-id']);
|
66 |
+
}
|
67 |
+
|
68 |
+
if (!in_array($dirIdOfHtaccess, ['uploads', 'cache'])) {
|
69 |
+
throw new \Exception('invalid htaccess directory id argument. It must be either "uploads" or "cache".');
|
70 |
+
}
|
71 |
+
|
72 |
+
|
73 |
+
// First try ENV
|
74 |
+
$destinationRelHtaccess = self::getEnvPassedInRewriteRule('WE_DESTINATION_REL_HTACCESS');
|
75 |
+
|
76 |
+
// Otherwise use query-string
|
77 |
+
if ($destinationRelHtaccess === false) {
|
78 |
+
if (isset($_GET['xdestination-rel-htaccess'])) {
|
79 |
+
$x = SanityCheck::noControlChars($_GET['xdestination-rel-htaccess']);
|
80 |
+
$destinationRelHtaccess = SanityCheck::pathWithoutDirectoryTraversal(substr($x, 1));
|
81 |
+
} else {
|
82 |
+
throw new \Exception('Argument for destination path is missing');
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
$destinationRelHtaccess = SanityCheck::pathWithoutDirectoryTraversal($destinationRelHtaccess);
|
87 |
+
|
88 |
+
$imageRoots = self::getImageRootsDef();
|
89 |
+
if ($dirIdOfHtaccess == 'uploads') {
|
90 |
+
return $imageRoots->byId('uploads')->getAbsPath() . '/' . $destinationRelHtaccess;
|
91 |
+
} elseif ($dirIdOfHtaccess == 'cache') {
|
92 |
+
return $imageRoots->byId('wp-content')->getAbsPath() . '/webp-express/webp-images/' . $destinationRelHtaccess;
|
93 |
+
}
|
94 |
+
/*
|
95 |
+
$pathTokens = explode('/', $destinationRelCacheRoot);
|
96 |
+
$imageRootId = array_shift($pathTokens);
|
97 |
+
$destinationRelSpecificCacheRoot = implode('/', $pathTokens);
|
98 |
+
|
99 |
+
$imageRootId = SanityCheck::pregMatch(
|
100 |
+
'#^[a-z\-]+$#',
|
101 |
+
$imageRootId,
|
102 |
+
'The image root ID is not a valid root id'
|
103 |
+
);
|
104 |
+
|
105 |
+
// TODO: Validate that the root id is in scope
|
106 |
+
|
107 |
+
if (count($pathTokens) == 0) {
|
108 |
+
throw new \Exception('invalid destination argument. It must contain dashes.');
|
109 |
+
}
|
110 |
+
|
111 |
+
return $imageRoots->byId($imageRootId)->getAbsPath() . '/' . $destinationRelSpecificCacheRoot;
|
112 |
+
|
113 |
+
/*
|
114 |
+
if ($imageRootId !== false) {
|
115 |
+
|
116 |
+
//$imageRootId = self::getEnvPassedInRewriteRule('WE_IMAGE_ROOT_ID');
|
117 |
+
if ($imageRootId !== false) {
|
118 |
+
$imageRootId = SanityCheck::pregMatch('#^[a-z\-]+$#', $imageRootId, 'The image root ID passed in ENV is not a valid root-id');
|
119 |
+
|
120 |
+
$destinationRelImageRoot = self::getEnvPassedInRewriteRule('WE_DESTINATION_REL_IMAGE_ROOT');
|
121 |
+
if ($destinationRelImageRoot !== false) {
|
122 |
+
$destinationRelImageRoot = SanityCheck::pathWithoutDirectoryTraversal($destinationRelImageRoot);
|
123 |
+
}
|
124 |
+
$imageRoots = self::getImageRootsDef();
|
125 |
+
return $imageRoots->byId($imageRootId)->getAbsPath() . '/' . $destinationRelImageRoot;
|
126 |
+
}
|
127 |
+
|
128 |
+
if (isset($_GET['xdestination-rel-image-root'])) {
|
129 |
+
$xdestinationRelImageRoot = SanityCheck::noControlChars($_GET['xdestination-rel-image-root']);
|
130 |
+
$destinationRelImageRoot = SanityCheck::pathWithoutDirectoryTraversal(substr($xdestinationRelImageRoot, 1));
|
131 |
+
|
132 |
+
$imageRootId = SanityCheck::noControlChars($_GET['image-root-id']);
|
133 |
+
SanityCheck::pregMatch('#^[a-z\-]+$#', $imageRootId, 'Not a valid root-id');
|
134 |
+
|
135 |
+
$imageRoots = self::getImageRootsDef();
|
136 |
+
return $imageRoots->byId($imageRootId)->getAbsPath() . '/' . $destinationRelImageRoot;
|
137 |
+
}
|
138 |
+
|
139 |
+
throw new \Exception('Argument for destination file missing');
|
140 |
+
//WE_DESTINATION_REL_IMG_ROOT*/
|
141 |
+
|
142 |
+
/*
|
143 |
+
$destAbs = SanityCheck::noControlChars(self::getEnvPassedInRewriteRule('WEDESTINATIONABS'));
|
144 |
+
if ($destAbs !== false) {
|
145 |
+
return SanityCheck::pathWithoutDirectoryTraversal($destAbs);
|
146 |
+
}
|
147 |
+
|
148 |
+
// Check querystring (relative path)
|
149 |
+
if (isset($_GET['xdest-rel-to-root-id'])) {
|
150 |
+
$xdestRelToRootId = SanityCheck::noControlChars($_GET['xdest-rel-to-root-id']);
|
151 |
+
$destRelToRootId = SanityCheck::pathWithoutDirectoryTraversal(substr($xdestRelToRootId, 1));
|
152 |
+
|
153 |
+
$rootId = SanityCheck::noControlChars($_GET['root-id']);
|
154 |
+
SanityCheck::pregMatch('#^[a-z]+$#', $rootId, 'Not a valid root-id');
|
155 |
+
return self::getRootPathById($rootId) . '/' . $destRelToRootId;
|
156 |
+
}
|
157 |
+
*/
|
158 |
+
|
159 |
+
}
|
160 |
+
|
161 |
+
private static function getDestination() {
|
162 |
+
self::$checking = 'destination path';
|
163 |
+
if (self::$usingDocRoot) {
|
164 |
+
$destination = self::getDestinationDocRoot();
|
165 |
+
} else {
|
166 |
+
$destination = self::getDestinationNoDocRoot();
|
167 |
+
}
|
168 |
+
SanityCheck::pregMatch('#\.webp$#', $destination, 'Does not end with .webp');
|
169 |
+
|
170 |
+
return $destination;
|
171 |
+
}
|
172 |
+
|
173 |
+
private static function processRequestNoTryCatch() {
|
174 |
+
|
175 |
+
self::loadConfig();
|
176 |
+
|
177 |
+
$options = self::$options;
|
178 |
+
$wodOptions = self::$wodOptions;
|
179 |
+
$serveOptions = $options['webp-convert'];
|
180 |
+
$convertOptions = &$serveOptions['convert'];
|
181 |
+
//echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
|
182 |
+
|
183 |
+
|
184 |
+
// Validate that WebPExpress was configured to redirect to this conversion script
|
185 |
+
// (but do not require that for Nginx)
|
186 |
+
// ------------------------------------------------------------------------------
|
187 |
+
self::$checking = 'settings';
|
188 |
+
if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
189 |
+
if (!isset($wodOptions['enable-redirection-to-webp-realizer']) || ($wodOptions['enable-redirection-to-webp-realizer'] === false)) {
|
190 |
+
throw new ValidateException('Redirection to webp realizer is not enabled');
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
// Get destination
|
195 |
+
// --------------------------------------------
|
196 |
+
self::$checking = 'destination';
|
197 |
+
$destination = self::getDestination();
|
198 |
+
|
199 |
+
//self::exitWithError($destination);
|
200 |
+
|
201 |
+
// Validate source path
|
202 |
+
// --------------------------------------------
|
203 |
+
$checking = 'source path';
|
204 |
+
$source = ConvertHelperIndependent::findSource(
|
205 |
+
$destination,
|
206 |
+
$wodOptions['destination-folder'],
|
207 |
+
$wodOptions['destination-extension'],
|
208 |
+
self::$usingDocRoot ? 'doc-root' : 'image-roots',
|
209 |
+
self::$webExpressContentDirAbs,
|
210 |
+
self::getImageRootsDef()
|
211 |
+
);
|
212 |
+
//self::exitWithError('source:' . $source);
|
213 |
+
//echo '<h3>destination:</h3> ' . $destination . '<h3>source:</h3>' . $source; exit;
|
214 |
+
|
215 |
+
if ($source === false) {
|
216 |
+
header('X-WebP-Express-Error: webp-realizer.php could not find an existing jpg/png that corresponds to the webp requested', true);
|
217 |
+
|
218 |
+
$protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
|
219 |
+
header($protocol . " 404 Not Found");
|
220 |
+
die();
|
221 |
+
//echo 'destination requested:<br><i>' . $destination . '</i>';
|
222 |
+
}
|
223 |
+
//$source = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
224 |
+
|
225 |
+
// Done with sanitizing, lets get to work!
|
226 |
+
// ---------------------------------------
|
227 |
+
$serveOptions['add-vary-header'] = false;
|
228 |
+
$serveOptions['fail'] = '404';
|
229 |
+
$serveOptions['fail-when-fail-fails'] = '404';
|
230 |
+
$serveOptions['serve-image']['headers']['vary-accept'] = false;
|
231 |
+
|
232 |
+
ConvertHelperIndependent::serveConverted(
|
233 |
+
$source,
|
234 |
+
$destination,
|
235 |
+
$serveOptions,
|
236 |
+
self::$webExpressContentDirAbs . '/log',
|
237 |
+
'Conversion triggered with the conversion script (wod/webp-realizer.php)'
|
238 |
+
);
|
239 |
+
|
240 |
+
}
|
241 |
+
|
242 |
+
public static function processRequest() {
|
243 |
+
try {
|
244 |
+
self::processRequestNoTryCatch();
|
245 |
+
} catch (SanityException $e) {
|
246 |
+
self::exitWithError('Sanity check failed for ' . self::$checking . ': '. $e->getMessage());
|
247 |
+
} catch (ValidateException $e) {
|
248 |
+
self::exitWithError('Validation failed for ' . self::$checking . ': '. $e->getMessage());
|
249 |
+
} catch (\Exception $e) {
|
250 |
+
self::exitWithError('Error occured while calculating ' . self::$checking . ': '. $e->getMessage());
|
251 |
+
}
|
252 |
+
}
|
253 |
+
}
|
lib/classes/WodConfigLoader.php
ADDED
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
This class is used by wod/webp-on-demand.php, which does not do a Wordpress bootstrap, but does register an autoloader for
|
4 |
+
the WebPExpress classes.
|
5 |
+
|
6 |
+
Calling Wordpress functions will FAIL. Make sure not to do that in either this class or the helpers.
|
7 |
+
*/
|
8 |
+
//error_reporting(E_ALL);
|
9 |
+
//ini_set('display_errors', 1);
|
10 |
+
|
11 |
+
namespace WebPExpress;
|
12 |
+
|
13 |
+
use \WebPExpress\ImageRoots;
|
14 |
+
use \WebPExpress\Sanitize;
|
15 |
+
use \WebPExpress\SanityCheck;
|
16 |
+
use \WebPExpress\SanityException;
|
17 |
+
use \WebPExpress\ValidateException;
|
18 |
+
use \WebPExpress\Validate;
|
19 |
+
|
20 |
+
class WodConfigLoader
|
21 |
+
{
|
22 |
+
|
23 |
+
protected static $docRoot;
|
24 |
+
protected static $checking;
|
25 |
+
protected static $wodOptions;
|
26 |
+
protected static $options;
|
27 |
+
protected static $usingDocRoot;
|
28 |
+
protected static $webExpressContentDirAbs;
|
29 |
+
|
30 |
+
public static function exitWithError($msg) {
|
31 |
+
header('X-WebP-Express-Error: ' . $msg, true);
|
32 |
+
echo $msg;
|
33 |
+
exit;
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Get environment variable set with mod_rewrite module
|
38 |
+
* Return false if the environment variable isn't found
|
39 |
+
*/
|
40 |
+
protected static function getEnvPassedInRewriteRule($envName) {
|
41 |
+
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
|
42 |
+
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
43 |
+
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
44 |
+
// We simply look for an environment variable that ends with what we are looking for.
|
45 |
+
// (so make sure to make it unique)
|
46 |
+
$len = strlen($envName);
|
47 |
+
foreach ($_SERVER as $key => $item) {
|
48 |
+
if (substr($key, -$len) == $envName) {
|
49 |
+
return $item;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
return false;
|
53 |
+
}
|
54 |
+
|
55 |
+
protected static function getWebPExpressContentDirWithDocRoot()
|
56 |
+
{
|
57 |
+
// Get relative path to wp-content
|
58 |
+
// --------------------------------
|
59 |
+
self::$checking = 'Relative path to wp-content dir';
|
60 |
+
|
61 |
+
// Passed in env variable?
|
62 |
+
$wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
|
63 |
+
if ($wpContentDirRel === false) {
|
64 |
+
// Passed in QS?
|
65 |
+
if (isset($_GET['wp-content'])) {
|
66 |
+
$wpContentDirRel = SanityCheck::pathWithoutDirectoryTraversal($_GET['wp-content']);
|
67 |
+
} else {
|
68 |
+
// In case above fails, fall back to standard location
|
69 |
+
$wpContentDirRel = 'wp-content';
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
// Check WebP Express content dir
|
74 |
+
// ---------------------------------
|
75 |
+
self::$checking = 'WebP Express content dir';
|
76 |
+
|
77 |
+
$webExpressContentDirAbs = SanityCheck::absPathExistsAndIsDir(self::$docRoot . '/' . $wpContentDirRel . '/webp-express');
|
78 |
+
return $webExpressContentDirAbs;
|
79 |
+
}
|
80 |
+
|
81 |
+
protected static function getWebPExpressContentDirNoDocRoot() {
|
82 |
+
// Check wp-content
|
83 |
+
// ----------------------
|
84 |
+
self::$checking = 'path to wp-content dir';
|
85 |
+
|
86 |
+
// Passed in env variable?
|
87 |
+
$wpContentDirRelToPluginDir = self::getEnvPassedInRewriteRule('WE_WP_CONTENT_REL_TO_PLUGIN_DIR');
|
88 |
+
if ($wpContentDirRelToPluginDir === false) {
|
89 |
+
// Passed in QS?
|
90 |
+
if (isset($_GET['xwp-content-rel-to-plugin-dir'])) {
|
91 |
+
|
92 |
+
$xwpContentDirRelToPluginDir = SanityCheck::noControlChars($_GET['xwp-content-rel-to-plugin-dir']);
|
93 |
+
$wpContentDirRelToPluginDir = SanityCheck::pathDirectoryTraversalAllowed(substr($xwpContentDirRelToPluginDir, 1));
|
94 |
+
|
95 |
+
} else {
|
96 |
+
throw new \Exception('Path to wp-content was not received');
|
97 |
+
}
|
98 |
+
}
|
99 |
+
|
100 |
+
// Check WebP Express content dir
|
101 |
+
// ---------------------------------
|
102 |
+
self::$checking = 'WebP Express content dir';
|
103 |
+
// echo 'dir:' . $wpContentDirRelToPluginDir . '<br>'; exit;
|
104 |
+
|
105 |
+
$pathToPluginDir = dirname(dirname(dirname(__DIR__)));
|
106 |
+
//echo $pathToPluginDir; exit;
|
107 |
+
|
108 |
+
$webExpressContentDirAbs = SanityCheck::pathDirectoryTraversalAllowed($pathToPluginDir . '/' . $wpContentDirRelToPluginDir . '/webp-express');
|
109 |
+
//echo $webExpressContentDirAbs; exit;
|
110 |
+
if (@!file_exists($webExpressContentDirAbs)) {
|
111 |
+
throw new \Exception('Dir not found');
|
112 |
+
}
|
113 |
+
$webExpressContentDirAbs = @realpath($webExpressContentDirAbs);
|
114 |
+
if ($webExpressContentDirAbs === false) {
|
115 |
+
throw new \Exception('WebP Express content dir is outside restricted open_basedir!');
|
116 |
+
}
|
117 |
+
return $webExpressContentDirAbs;
|
118 |
+
}
|
119 |
+
|
120 |
+
protected static function getImageRootsDef()
|
121 |
+
{
|
122 |
+
if (!isset(self::$wodOptions['image-roots'])) {
|
123 |
+
throw new \Exception('No image roots defined in config.');
|
124 |
+
}
|
125 |
+
return new ImageRoots(self::$wodOptions['image-roots']);
|
126 |
+
}
|
127 |
+
|
128 |
+
protected static function loadConfig() {
|
129 |
+
|
130 |
+
$usingDocRoot = !(
|
131 |
+
isset($_GET['xwp-content-rel-to-plugin-dir']) ||
|
132 |
+
self::getEnvPassedInRewriteRule('WE_WP_CONTENT_REL_TO_PLUGIN_DIR')
|
133 |
+
);
|
134 |
+
self::$usingDocRoot = $usingDocRoot;
|
135 |
+
|
136 |
+
if ($usingDocRoot) {
|
137 |
+
// Check DOCUMENT_ROOT
|
138 |
+
// ----------------------
|
139 |
+
self::$checking = 'DOCUMENT_ROOT';
|
140 |
+
$docRootAvailable = PathHelper::isDocRootAvailableAndResolvable();
|
141 |
+
if (!$docRootAvailable) {
|
142 |
+
throw new \Exception(
|
143 |
+
'Document root is no longer available. It was available when the .htaccess rules was created and ' .
|
144 |
+
'the rules are based on that. You need to regenerate the rules (or fix your document root configuration)'
|
145 |
+
);
|
146 |
+
}
|
147 |
+
|
148 |
+
$docRoot = SanityCheck::absPath($_SERVER["DOCUMENT_ROOT"]);
|
149 |
+
$docRoot = rtrim($docRoot, '/');
|
150 |
+
self::$docRoot = $docRoot;
|
151 |
+
}
|
152 |
+
|
153 |
+
if ($usingDocRoot) {
|
154 |
+
self::$webExpressContentDirAbs = self::getWebPExpressContentDirWithDocRoot();
|
155 |
+
} else {
|
156 |
+
self::$webExpressContentDirAbs = self::getWebPExpressContentDirNoDocRoot();
|
157 |
+
}
|
158 |
+
|
159 |
+
// Check config file name
|
160 |
+
// ---------------------------------
|
161 |
+
self::$checking = 'config file';
|
162 |
+
|
163 |
+
$configFilename = self::$webExpressContentDirAbs . '/config/wod-options.json';
|
164 |
+
if (!file_exists($configFilename)) {
|
165 |
+
throw new \Exception('Configuration file was not found (wod-options.json)');
|
166 |
+
}
|
167 |
+
|
168 |
+
// Check config file
|
169 |
+
// --------------------
|
170 |
+
$configLoadResult = file_get_contents($configFilename);
|
171 |
+
if ($configLoadResult === false) {
|
172 |
+
throw new \Exception('Cannot open config file');
|
173 |
+
}
|
174 |
+
$json = SanityCheck::isJSONObject($configLoadResult);
|
175 |
+
|
176 |
+
self::$options = json_decode($json, true);
|
177 |
+
self::$wodOptions = self::$options['wod'];
|
178 |
+
}
|
179 |
+
|
180 |
+
}
|
lib/dismissable-messages/0.15.0/new-scope-setting-content.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
namespace WebPExpress;
|
3 |
+
|
4 |
+
// introduced in 0.14.0 (migrate 9)
|
5 |
+
DismissableMessages::printDismissableMessage(
|
6 |
+
'info',
|
7 |
+
'WebP Express 0.15 introduced a new "scope" setting which determines which folders that WebP Express ' .
|
8 |
+
'operates in. It has been set to "All content" in order not to change behaviour. ' .
|
9 |
+
'However, I would usually recommend limitting scope to "Uploads and Themes".',
|
10 |
+
'0.15.0/new-scope-setting-content',
|
11 |
+
'Got it!'
|
12 |
+
);
|
lib/dismissable-messages/0.15.0/new-scope-setting-index.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
// introduced in 0.14.0 (migrate 9)
|
6 |
+
DismissableMessages::printDismissableMessage(
|
7 |
+
'info',
|
8 |
+
'WebP Express 0.15 introduced a new "scope" setting which determines which folders that WebP Express ' .
|
9 |
+
'operates in. It has been set to work on "index" (any images in the whole install, including wp-adimn) ' .
|
10 |
+
'in order not to change the behaviour. However, I would usually recommend a more limitted scope, ie. "Uploads and Themes".',
|
11 |
+
'0.15.0/new-scope-setting-index',
|
12 |
+
'Got it!'
|
13 |
+
);
|
lib/dismissable-messages/0.15.0/new-scope-setting-no-uploads.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
DismissableMessages::printDismissableMessage(
|
6 |
+
'info',
|
7 |
+
'WebP Express 0.15 introduced a new "scope" setting which determines which folders that WebP Express ' .
|
8 |
+
'operates in. The migration script did not set it to include "uploads" because it seemed that it ' .
|
9 |
+
'would not be possible to write the .htaccess rules there.',
|
10 |
+
'0.15.0/new-scope-setting-no-uploads',
|
11 |
+
'Got it!'
|
12 |
+
);
|
lib/migrate/migrate11.php
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPExpress;
|
4 |
+
|
5 |
+
use \WebPExpress\Config;
|
6 |
+
use \WebPExpress\Messenger;
|
7 |
+
|
8 |
+
function webpexpress_migrate11() {
|
9 |
+
|
10 |
+
$config = Config::loadConfigAndFix(false); // false, because we do not need to test if quality detection is working
|
11 |
+
|
12 |
+
// There is a new option: "scope"
|
13 |
+
// We want to set it to match current setup as closely as possible.
|
14 |
+
|
15 |
+
$rootsWithRulesIn = HTAccess::getRootsWithWebPExpressRulesIn();
|
16 |
+
|
17 |
+
if (in_array('index', $rootsWithRulesIn)) {
|
18 |
+
$scope = ['index', 'plugins', 'themes', 'uploads', 'wp-content'];
|
19 |
+
} elseif (in_array('wp-content', $rootsWithRulesIn)) {
|
20 |
+
$scope = ['plugins', 'themes', 'uploads', 'wp-content'];
|
21 |
+
} else {
|
22 |
+
$scope = ['themes', 'uploads'];
|
23 |
+
}
|
24 |
+
|
25 |
+
// However, if some of the roots cannot be used, we remove these.
|
26 |
+
|
27 |
+
$scope2 = [];
|
28 |
+
foreach ($scope as $rootId) {
|
29 |
+
if (Paths::canWriteHTAccessRulesInDir($rootId)) {
|
30 |
+
$scope2[] = $rootId;
|
31 |
+
}
|
32 |
+
}
|
33 |
+
if (count($scope2) == 0) {
|
34 |
+
Messenger::addMessage(
|
35 |
+
'warning',
|
36 |
+
'WebP Express cannot update the .htaccess rules that it needs to. ' .
|
37 |
+
'Please go to WebP Express settings and click "Save settings and force new .htaccess rules".'
|
38 |
+
);
|
39 |
+
$scope2 = ['themes', 'uploads'];
|
40 |
+
}
|
41 |
+
|
42 |
+
$config['scope'] = $scope2;
|
43 |
+
|
44 |
+
if (in_array('index', $config['scope'])) {
|
45 |
+
DismissableMessages::addDismissableMessage('0.15.0/new-scope-setting-index');
|
46 |
+
} elseif (in_array('wp-content', $config['scope'])) {
|
47 |
+
DismissableMessages::addDismissableMessage('0.15.0/new-scope-setting-content');
|
48 |
+
} elseif (!in_array('uploads', $config['scope'])) {
|
49 |
+
DismissableMessages::addDismissableMessage('0.15.0/new-scope-setting-no-uploads');
|
50 |
+
}
|
51 |
+
|
52 |
+
/*
|
53 |
+
error_log('roots with rules:' . implode(',', $rootsWithRulesIn));
|
54 |
+
error_log('scope:' . implode(',', $config['scope']));
|
55 |
+
error_log('scope2:' . implode(',', $scope2));*/
|
56 |
+
|
57 |
+
|
58 |
+
$forceHtaccessRegeneration = true;
|
59 |
+
$result = Config::saveConfigurationAndHTAccess($config, $forceHtaccessRegeneration);
|
60 |
+
|
61 |
+
if ($result['saved-both-config']) {
|
62 |
+
Messenger::addMessage(
|
63 |
+
'info',
|
64 |
+
'Successfully migrated <i>WebP Express</i> options for 0.15.0.'
|
65 |
+
);
|
66 |
+
Option::updateOption('webp-express-migration-version', '11');
|
67 |
+
|
68 |
+
} else {
|
69 |
+
Messenger::addMessage(
|
70 |
+
'error',
|
71 |
+
'Failed migrating webp express options to 0.15.0. Probably you need to grant write permissions in your wp-content folder.'
|
72 |
+
);
|
73 |
+
}
|
74 |
+
|
75 |
+
}
|
76 |
+
|
77 |
+
webpexpress_migrate11();
|
lib/migrate/migrate7.php
CHANGED
@@ -36,7 +36,7 @@ function webpexpress_migrate7() {
|
|
36 |
if (Config::saveConfigurationFileAndWodOptions($config)) {
|
37 |
|
38 |
$msg = 'Successfully migrated <i>WebP Express</i> options for 0.12. ';
|
39 |
-
// The webp realizer rules where errornous, so recreate rules, if
|
40 |
|
41 |
if (($config['enable-redirection-to-webp-realizer']) && ($config['destination-folder'] != 'mingled')) {
|
42 |
HTAccess::saveRules($config);
|
36 |
if (Config::saveConfigurationFileAndWodOptions($config)) {
|
37 |
|
38 |
$msg = 'Successfully migrated <i>WebP Express</i> options for 0.12. ';
|
39 |
+
// The webp realizer rules where errornous, so recreate rules, if necessary. (see issue #195)
|
40 |
|
41 |
if (($config['enable-redirection-to-webp-realizer']) && ($config['destination-folder'] != 'mingled')) {
|
42 |
HTAccess::saveRules($config);
|
lib/options/css/webp-express-options-page.css
CHANGED
@@ -9,6 +9,42 @@
|
|
9 |
word-wrap: break-word;
|
10 |
}
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
|
14 |
|
@@ -178,9 +214,27 @@
|
|
178 |
.help .popup.wider {
|
179 |
width: 450px;
|
180 |
}
|
181 |
-
.help .popup.
|
182 |
width: 600px;
|
183 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
@media (max-width: 500px) {
|
185 |
.help .popup {
|
186 |
max-width: 380px;
|
@@ -586,5 +640,5 @@ ul.with-bullets {
|
|
586 |
bottom: 72px;
|
587 |
position: absolute;
|
588 |
right: 0;
|
589 |
-
left: 3%;
|
590 |
}
|
9 |
word-wrap: break-word;
|
10 |
}
|
11 |
|
12 |
+
/* Classes used in our pseudo markdown. */
|
13 |
+
.webpexpress.md h1 {
|
14 |
+
margin: 1em 0 1em;
|
15 |
+
font-size: 2em;
|
16 |
+
font-weight: normal;
|
17 |
+
}
|
18 |
+
.webpexpress.md h2 {
|
19 |
+
margin: 1em 0 0.5em;
|
20 |
+
font-size: 1.6em;
|
21 |
+
font-weight: normal;
|
22 |
+
text-transform: uppercase;
|
23 |
+
text-align: center;
|
24 |
+
padding: 10px 0;
|
25 |
+
background-color: #ccc;
|
26 |
+
}
|
27 |
+
.webpexpress.md h3 {
|
28 |
+
margin: 1em 0 0.5em;
|
29 |
+
font-size: 1.4em;
|
30 |
+
font-weight: normal;
|
31 |
+
}
|
32 |
+
.webpexpress.md h4 {
|
33 |
+
font-size: 1.0em;
|
34 |
+
margin: 1em 0 0.5em;
|
35 |
+
/*font-weight: 500;*/
|
36 |
+
font-weight: normal;
|
37 |
+
font-style: italic;
|
38 |
+
}
|
39 |
+
.webpexpress.md .warn {
|
40 |
+
background-color: yellow;
|
41 |
+
}
|
42 |
+
.webpexpress.md .ok {
|
43 |
+
color: green;
|
44 |
+
}
|
45 |
+
.webpexpress.md .error {
|
46 |
+
color: red;
|
47 |
+
}
|
48 |
|
49 |
|
50 |
|
214 |
.help .popup.wider {
|
215 |
width: 450px;
|
216 |
}
|
217 |
+
.help .popup.even-wider {
|
218 |
width: 600px;
|
219 |
}
|
220 |
+
.help .popup.widest {
|
221 |
+
width: 750px;
|
222 |
+
}
|
223 |
+
@media (max-width: 1150px) {
|
224 |
+
.help .popup {
|
225 |
+
max-width: 620px;
|
226 |
+
}
|
227 |
+
}
|
228 |
+
@media (max-width: 900px) {
|
229 |
+
.help .popup {
|
230 |
+
max-width: 560px;
|
231 |
+
}
|
232 |
+
}
|
233 |
+
@media (max-width: 830px) {
|
234 |
+
.help .popup {
|
235 |
+
max-width: 530px;
|
236 |
+
}
|
237 |
+
}
|
238 |
@media (max-width: 500px) {
|
239 |
.help .popup {
|
240 |
max-width: 380px;
|
640 |
bottom: 72px;
|
641 |
position: absolute;
|
642 |
right: 0;
|
643 |
+
left: 3%;
|
644 |
}
|
lib/options/enqueue_scripts.php
CHANGED
@@ -2,14 +2,11 @@
|
|
2 |
|
3 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
4 |
|
5 |
-
include_once __DIR__ . '/../classes/Paths.php';
|
6 |
use \WebPExpress\Paths;
|
7 |
-
|
8 |
-
include_once __DIR__ . '/../classes/Config.php';
|
9 |
use \WebPExpress\Config;
|
10 |
|
11 |
-
$ver = '
|
12 |
-
$jsDir = 'js/0.
|
13 |
|
14 |
if (!function_exists('webp_express_add_inline_script')) {
|
15 |
function webp_express_add_inline_script($id, $script, $position) {
|
@@ -33,6 +30,9 @@ wp_enqueue_script('escapehtml');
|
|
33 |
|
34 |
$config = Config::getConfigForOptionsPage();
|
35 |
|
|
|
|
|
|
|
36 |
|
37 |
// Add converter, bulk convert and whitelist script, EXCEPT for "no conversion" mode
|
38 |
if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conversion'))) {
|
@@ -51,10 +51,18 @@ if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conv
|
|
51 |
wp_register_script('converters', plugins_url($jsDir . '/converters.js', __FILE__), ['sortable', 'daspopup', 'escapehtml'], $ver);
|
52 |
|
53 |
// PS: no escaping/sanitizing needed as json_encode always produces something safe
|
54 |
-
webp_express_add_inline_script(
|
|
|
|
|
|
|
|
|
55 |
|
56 |
// PS: no escaping/sanitizing needed as json_encode always produces something safe
|
57 |
-
webp_express_add_inline_script(
|
|
|
|
|
|
|
|
|
58 |
wp_enqueue_script('converters');
|
59 |
|
60 |
// Whitelist
|
@@ -76,6 +84,7 @@ if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conv
|
|
76 |
/*
|
77 |
AlterHTMLHelper::getWebPUrlInBase(
|
78 |
Paths::getPluginUrl() . '/webp-express', // source url
|
|
|
79 |
Paths::getPluginUrl(), // base url
|
80 |
Paths::getPluginDirAbs() // base dir
|
81 |
);
|
@@ -99,12 +108,21 @@ if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conv
|
|
99 |
//wp_enqueue_script('api_keys');
|
100 |
|
101 |
wp_register_script( 'page', plugins_url($jsDir . '/page.js', __FILE__), [], $ver);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
webp_express_add_inline_script(
|
103 |
'page',
|
104 |
-
'window.
|
105 |
-
'window.webpExpressAjaxListUnconvertedFilesNonce = "' . wp_create_nonce('webpexpress-ajax-list-unconverted-files-nonce') . '";' .
|
106 |
-
'window.webpExpressAjaxPurgeCacheNonce = "' . wp_create_nonce('webpexpress-ajax-purge-cache-nonce') . '";' .
|
107 |
-
'window.webpExpressAjaxViewLogNonce = "' . wp_create_nonce('webpexpress-ajax-view-log-nonce') . '";',
|
108 |
'before'
|
109 |
);
|
110 |
wp_enqueue_script('page');
|
2 |
|
3 |
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
|
4 |
|
|
|
5 |
use \WebPExpress\Paths;
|
|
|
|
|
6 |
use \WebPExpress\Config;
|
7 |
|
8 |
+
$ver = '1'; // note: Minimum 1
|
9 |
+
$jsDir = 'js/0.15.0'; // We change dir when it is critical that no-one gets the cached version (there is a plugin that strips version strings out there...)
|
10 |
|
11 |
if (!function_exists('webp_express_add_inline_script')) {
|
12 |
function webp_express_add_inline_script($id, $script, $position) {
|
30 |
|
31 |
$config = Config::getConfigForOptionsPage();
|
32 |
|
33 |
+
// selftest
|
34 |
+
wp_register_script('webpexpress_selftest', plugins_url($jsDir . '/self-test.js', __FILE__), ['escapehtml'], $ver);
|
35 |
+
wp_enqueue_script('webpexpress_selftest');
|
36 |
|
37 |
// Add converter, bulk convert and whitelist script, EXCEPT for "no conversion" mode
|
38 |
if (!(isset($config['operation-mode']) && ($config['operation-mode'] == 'no-conversion'))) {
|
51 |
wp_register_script('converters', plugins_url($jsDir . '/converters.js', __FILE__), ['sortable', 'daspopup', 'escapehtml'], $ver);
|
52 |
|
53 |
// PS: no escaping/sanitizing needed as json_encode always produces something safe
|
54 |
+
webp_express_add_inline_script(
|
55 |
+
'converters',
|
56 |
+
'window.webpExpressPaths = ' . json_encode(Paths::getUrlsAndPathsForTheJavascript()) . ';',
|
57 |
+
'before'
|
58 |
+
);
|
59 |
|
60 |
// PS: no escaping/sanitizing needed as json_encode always produces something safe
|
61 |
+
webp_express_add_inline_script(
|
62 |
+
'converters',
|
63 |
+
'window.converters = ' . json_encode($config['converters']) . ';',
|
64 |
+
'before'
|
65 |
+
);
|
66 |
wp_enqueue_script('converters');
|
67 |
|
68 |
// Whitelist
|
84 |
/*
|
85 |
AlterHTMLHelper::getWebPUrlInBase(
|
86 |
Paths::getPluginUrl() . '/webp-express', // source url
|
87 |
+
$baseId,
|
88 |
Paths::getPluginUrl(), // base url
|
89 |
Paths::getPluginDirAbs() // base dir
|
90 |
);
|
108 |
//wp_enqueue_script('api_keys');
|
109 |
|
110 |
wp_register_script( 'page', plugins_url($jsDir . '/page.js', __FILE__), [], $ver);
|
111 |
+
|
112 |
+
// TODO: Add all vars needed to this array (whitelist, converters, etc)
|
113 |
+
$javascriptVars = [
|
114 |
+
'ajax-nonces' => [
|
115 |
+
'convert' => wp_create_nonce('webpexpress-ajax-convert-nonce'),
|
116 |
+
'list-unconverted-files' => wp_create_nonce('webpexpress-ajax-list-unconverted-files-nonce'),
|
117 |
+
'purge-cache' => wp_create_nonce('webpexpress-ajax-purge-cache-nonce'),
|
118 |
+
'view-log' => wp_create_nonce('webpexpress-ajax-view-log-nonce'),
|
119 |
+
'self-test' => wp_create_nonce('webpexpress-ajax-self-test-nonce'),
|
120 |
+
],
|
121 |
+
'can-use-doc-root-for-structuring' => Paths::canUseDocRootForRelPaths()
|
122 |
+
];
|
123 |
webp_express_add_inline_script(
|
124 |
'page',
|
125 |
+
'window.webpExpress = ' . json_encode($javascriptVars, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK),
|
|
|
|
|
|
|
126 |
'before'
|
127 |
);
|
128 |
wp_enqueue_script('page');
|
lib/options/js/0.14.9/escapeHTML.js
DELETED
@@ -1,16 +0,0 @@
|
|
1 |
-
/*
|
2 |
-
function htmlEscape(str) {
|
3 |
-
return str
|
4 |
-
.replace(/&/g, '&')
|
5 |
-
.replace(/"/g, '"')
|
6 |
-
.replace(/'/g, ''')
|
7 |
-
.replace(/</g, '<')
|
8 |
-
.replace(/>/g, '>');
|
9 |
-
}
|
10 |
-
*/
|
11 |
-
function webpexpress_escapeHTML(s)
|
12 |
-
{
|
13 |
-
return s.replace(/./gm, function(s) {
|
14 |
-
return "&#" + s.charCodeAt(0) + ";";
|
15 |
-
});
|
16 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/options/js/{0.14.9 → 0.15.0}/authorized_sites_bak.js
RENAMED
File without changes
|
lib/options/js/{0.14.9 → 0.15.0}/bulk-convert.js
RENAMED
@@ -5,7 +5,7 @@ function openBulkConvertPopup() {
|
|
5 |
|
6 |
var data = {
|
7 |
'action': 'list_unconverted_files',
|
8 |
-
'nonce' : window.
|
9 |
};
|
10 |
jQuery.post(ajaxurl, data, function(response) {
|
11 |
if ((typeof response == 'object') && (response['success'] == false)) {
|
@@ -39,11 +39,11 @@ function openBulkConvertPopup() {
|
|
39 |
} else {
|
40 |
html += '<div>'
|
41 |
html += '<p>There are ' + numFiles + ' unconverted files.</p>';
|
42 |
-
html += '<p><i>Note that in a typical setup, you will have redirect rules which
|
43 |
'and thus you have no need for bulk conversion. In fact, in that case, you should probably not bulk convert ' +
|
44 |
'because bulk conversion will also convert images and thumbnails which are not in use, and thus take up ' +
|
45 |
-
'more disk space than
|
46 |
-
'there
|
47 |
'</i></p><br>';
|
48 |
html += '<button onclick="startBulkConversion()" class="button button-primary" type="button">Start conversion</button>';
|
49 |
html += '</div>';
|
@@ -71,10 +71,10 @@ function pauseOrResumeBulkConversion() {
|
|
71 |
function startBulkConversion() {
|
72 |
var html = '<br>';
|
73 |
html += '<style>' +
|
74 |
-
'.has-tip {cursor:pointer; position:
|
75 |
'.has-tip .tip {display: none}\n' +
|
76 |
'.has-tip:hover .tip {display: block}\n' +
|
77 |
-
'.tip{padding: 5px 10px; background-color:#ff9;
|
78 |
'.reduction {float:right;}\n' +
|
79 |
'</style>';
|
80 |
html += '<button id="bulkPauseResumeBtn" onclick="pauseOrResumeBulkConversion()" class="button button-primary" type="button">Pause</button>';
|
@@ -151,7 +151,7 @@ function webpexpress_viewLog(groupPointer, filePointer) {
|
|
151 |
url: ajaxurl,
|
152 |
data: {
|
153 |
'action': 'webpexpress_view_log',
|
154 |
-
'nonce' : window.
|
155 |
'source': source
|
156 |
},
|
157 |
success: (response) => {
|
@@ -218,7 +218,7 @@ function convertNextInBulkQueue() {
|
|
218 |
|
219 |
var data = {
|
220 |
'action': 'convert_file',
|
221 |
-
'nonce' : window.
|
222 |
'filename': group.root + '/' + filename
|
223 |
|
224 |
//'whatever': ajax_object.we_value // We pass php values differently!
|
@@ -262,14 +262,19 @@ function convertNextInBulkQueue() {
|
|
262 |
|
263 |
bulkInfo['orgTotalFilesize'] += orgSize;
|
264 |
bulkInfo['webpTotalFilesize'] += webpSize;
|
265 |
-
|
|
|
266 |
html += ' <span style="color:green">ok</span></span>' +
|
267 |
htmlViewLog +
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
|
270 |
-
|
271 |
-
//html += ' <span style="color:green" class="has-tip">ok<span class="tip">' + result['log'] + '</span></span>' +
|
272 |
-
// getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
|
273 |
} else {
|
274 |
html += ' <span style="color:red">failed</span>' + htmlViewLog;
|
275 |
/*
|
5 |
|
6 |
var data = {
|
7 |
'action': 'list_unconverted_files',
|
8 |
+
'nonce' : window.webpExpress['ajax-nonces']['list-unconverted-files'],
|
9 |
};
|
10 |
jQuery.post(ajaxurl, data, function(response) {
|
11 |
if ((typeof response == 'object') && (response['success'] == false)) {
|
39 |
} else {
|
40 |
html += '<div>'
|
41 |
html += '<p>There are ' + numFiles + ' unconverted files.</p>';
|
42 |
+
html += '<p><i>Note that in a typical setup, you will have redirect rules which trigger conversion when needed, ' +
|
43 |
'and thus you have no need for bulk conversion. In fact, in that case, you should probably not bulk convert ' +
|
44 |
'because bulk conversion will also convert images and thumbnails which are not in use, and thus take up ' +
|
45 |
+
'more disk space than necessary. The bulk conversion feature was only added in order to make the plugin usable even when ' +
|
46 |
+
'there are problems with redirects (ie on Nginx in case you do not have access to the config or on Microsoft IIS). ' +
|
47 |
'</i></p><br>';
|
48 |
html += '<button onclick="startBulkConversion()" class="button button-primary" type="button">Start conversion</button>';
|
49 |
html += '</div>';
|
71 |
function startBulkConversion() {
|
72 |
var html = '<br>';
|
73 |
html += '<style>' +
|
74 |
+
'.has-tip {cursor:pointer; position:static;}\n' +
|
75 |
'.has-tip .tip {display: none}\n' +
|
76 |
'.has-tip:hover .tip {display: block}\n' +
|
77 |
+
'.tip{padding: 5px 10px; background-color:#ff9;min-width:310px; font-size:10px; color: black; border:1px solid black; max-width:90%;z-index:10}\n' +
|
78 |
'.reduction {float:right;}\n' +
|
79 |
'</style>';
|
80 |
html += '<button id="bulkPauseResumeBtn" onclick="pauseOrResumeBulkConversion()" class="button button-primary" type="button">Pause</button>';
|
151 |
url: ajaxurl,
|
152 |
data: {
|
153 |
'action': 'webpexpress_view_log',
|
154 |
+
'nonce' : window.webpExpress['ajax-nonces']['view-log'],
|
155 |
'source': source
|
156 |
},
|
157 |
success: (response) => {
|
218 |
|
219 |
var data = {
|
220 |
'action': 'convert_file',
|
221 |
+
'nonce' : window.webpExpress['ajax-nonces']['convert'],
|
222 |
'filename': group.root + '/' + filename
|
223 |
|
224 |
//'whatever': ajax_object.we_value // We pass php values differently!
|
262 |
|
263 |
bulkInfo['orgTotalFilesize'] += orgSize;
|
264 |
bulkInfo['webpTotalFilesize'] += webpSize;
|
265 |
+
//'- Saved at: ' + result['destination-path'] +
|
266 |
+
/*
|
267 |
html += ' <span style="color:green">ok</span></span>' +
|
268 |
htmlViewLog +
|
269 |
+
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')*/
|
270 |
+
|
271 |
+
html += ' <span style="color:green" class="has-tip">ok' +
|
272 |
+
'<span class="tip">' +
|
273 |
+
'<b>Destination:</b><br>' + result['destination-path'] + '<br><br>' +
|
274 |
+
'<b>Url:</b><br><a href="' + result['destination-url'] + '">' + result['destination-url'] + '<br>' +
|
275 |
+
'</span>' +
|
276 |
+
'</span>' +
|
277 |
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
|
|
|
|
|
|
|
278 |
} else {
|
279 |
html += ' <span style="color:red">failed</span>' + htmlViewLog;
|
280 |
/*
|
lib/options/js/{0.14.9 → 0.15.0}/converters.js
RENAMED
@@ -417,53 +417,8 @@ function encodePathforQS(path) {
|
|
417 |
}
|
418 |
|
419 |
function testConverter(id) {
|
420 |
-
//alert('h' + id);
|
421 |
openTestConvertPopup(id);
|
422 |
return;
|
423 |
-
var converter = window.convertersMap[id];
|
424 |
-
|
425 |
-
// https://stackoverflow.com/questions/4321068/to-invoke-thickbox-using-javascript
|
426 |
-
|
427 |
-
var urls = window.webpExpressPaths['urls'];
|
428 |
-
var paths = window.webpExpressPaths['filePaths'];
|
429 |
-
|
430 |
-
var url = '/' + urls['webpExpressRoot'] + '/test/test-run.php';
|
431 |
-
//alert(url);
|
432 |
-
|
433 |
-
|
434 |
-
// test images here: http://nottinghamtec.co.uk/~aer/TestPatterns/1080/
|
435 |
-
filename = 'test.jpg';
|
436 |
-
/* filename = 'stones.jpg';
|
437 |
-
filename = 'architecture2.jpg';
|
438 |
-
filename = 'test1.png';
|
439 |
-
filename = 'focus.jpg';*/
|
440 |
-
|
441 |
-
url += '?source=' + encodePathforQS(paths['webpExpressRoot'] + '/test/' + filename);
|
442 |
-
url += '&configDirRel=' + encodePathforQS(paths['configRelToDocRoot']);
|
443 |
-
url += '&destination=' + encodePathforQS(paths['destinationRoot'] + '/test-conversions/' + filename + '.webp');
|
444 |
-
url += '&converter=' + converter['converter'];
|
445 |
-
if (document.getElementById('max_quality')) {
|
446 |
-
url += '&max-quality=' + document.getElementById('max_quality').value;
|
447 |
-
}
|
448 |
-
if (document.getElementById('quality_specific')) {
|
449 |
-
url += '&quality=' + document.getElementById('quality_specific').value;
|
450 |
-
}
|
451 |
-
//url += '&method=' + document.getElementsByName('webp_express_method')[0].value;
|
452 |
-
|
453 |
-
if (converter.options) {
|
454 |
-
for (var option in converter.options) {
|
455 |
-
if (converter.options.hasOwnProperty(option)) {
|
456 |
-
//alert(option);
|
457 |
-
url += '&' + option + '=' + converter.options[option];
|
458 |
-
}
|
459 |
-
}
|
460 |
-
}
|
461 |
-
var w = Math.min(1000, Math.max(200, document.documentElement.clientWidth - 100));
|
462 |
-
var h = Math.max(250, document.documentElement.clientHeight - 80);
|
463 |
-
|
464 |
-
url += '&TB_iframe=true&width=' + w + '&height=' + h;
|
465 |
-
//alert(url);
|
466 |
-
tb_show("Test running converter: " + converter['id'], url);
|
467 |
}
|
468 |
|
469 |
/*
|
417 |
}
|
418 |
|
419 |
function testConverter(id) {
|
|
|
420 |
openTestConvertPopup(id);
|
421 |
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
422 |
}
|
423 |
|
424 |
/*
|
lib/options/js/{0.14.9 → 0.15.0}/das-popup.js
RENAMED
File without changes
|
lib/options/js/0.15.0/escapeHTML.js
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
function htmlEscape(str) {
|
3 |
+
return str
|
4 |
+
.replace(/&/g, '&')
|
5 |
+
.replace(/"/g, '"')
|
6 |
+
.replace(/'/g, ''')
|
7 |
+
.replace(/</g, '<')
|
8 |
+
.replace(/>/g, '>');
|
9 |
+
}
|
10 |
+
*/
|
11 |
+
function webpexpress_escapeHTML(s)
|
12 |
+
{
|
13 |
+
return s.replace(/./gm, function(s) {
|
14 |
+
var safe = /[0-9a-zA-Z\!]/;
|
15 |
+
if (safe.test(s.charAt(0))) {
|
16 |
+
return s.charAt(0);
|
17 |
+
}
|
18 |
+
|
19 |
+
switch (s.charAt(0)) {
|
20 |
+
case '*':
|
21 |
+
case '#':
|
22 |
+
case ' ':
|
23 |
+
case '{':
|
24 |
+
case '}':
|
25 |
+
case ':':
|
26 |
+
case '.':
|
27 |
+
case '`':
|
28 |
+
return s.charAt(0);
|
29 |
+
default:
|
30 |
+
return "&#" + s.charCodeAt(0) + ";";
|
31 |
+
}
|
32 |
+
|
33 |
+
});
|
34 |
+
}
|
lib/options/js/{0.14.9 → 0.15.0}/image-comparison-slider.js
RENAMED
File without changes
|
lib/options/js/{0.14.9 → 0.15.0}/page.js
RENAMED
@@ -147,12 +147,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
147 |
|
148 |
// PNG encoding changing
|
149 |
if (el('image_types') && el('png_row')) {
|
150 |
-
function
|
151 |
-
|
|
|
|
|
|
|
|
|
152 |
}
|
153 |
-
|
154 |
el('image_types').addEventListener('change', function() {
|
155 |
-
|
156 |
});
|
157 |
}
|
158 |
|
@@ -180,13 +184,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
180 |
});
|
181 |
}
|
182 |
|
|
|
|
|
|
|
|
|
183 |
|
|
|
|
|
|
|
|
|
|
|
184 |
|
185 |
// Toggle File Extension (only show when "mingled" is selected)
|
186 |
-
if (el('destination_folder') && el('
|
187 |
-
el('
|
188 |
function updateDestinationExtensionVisibility() {
|
189 |
-
toggleVisibility('
|
190 |
|
191 |
if (el('destination_folder').value == 'separate') {
|
192 |
el('destination_extension').value = 'append';
|
147 |
|
148 |
// PNG encoding changing
|
149 |
if (el('image_types') && el('png_row')) {
|
150 |
+
function updatePngAndJpgRowVisibilities() {
|
151 |
+
var imageTypes = parseInt(el('image_types').value, 10);
|
152 |
+
var pngEnabled = (imageTypes & 2);
|
153 |
+
var jpegEnabled = (imageTypes & 1);
|
154 |
+
toggleVisibility('png_row', pngEnabled);
|
155 |
+
toggleVisibility('jpeg_row', jpegEnabled);
|
156 |
}
|
157 |
+
updatePngAndJpgRowVisibilities();
|
158 |
el('image_types').addEventListener('change', function() {
|
159 |
+
updatePngAndJpgRowVisibilities();
|
160 |
});
|
161 |
}
|
162 |
|
184 |
});
|
185 |
}
|
186 |
|
187 |
+
// If "doc-root" cannot be used for structuring, disable the option and set to "image-roots"
|
188 |
+
if (!window.webpExpress['can-use-doc-root-for-structuring']) {
|
189 |
+
el('destination_structure').classList.add('effect-opacity');
|
190 |
+
toggleVisibility('destination_structure', false);
|
191 |
|
192 |
+
if (el('destination_structure').value == 'doc-root') {
|
193 |
+
el('destination_structure').value = 'image-roots';
|
194 |
+
}
|
195 |
+
|
196 |
+
}
|
197 |
|
198 |
// Toggle File Extension (only show when "mingled" is selected)
|
199 |
+
if (el('destination_folder') && el('destination_extension')) {
|
200 |
+
el('destination_extension').classList.add('effect-opacity');
|
201 |
function updateDestinationExtensionVisibility() {
|
202 |
+
toggleVisibility('destination_extension', el('destination_folder').value == 'mingled');
|
203 |
|
204 |
if (el('destination_folder').value == 'separate') {
|
205 |
el('destination_extension').value = 'append';
|
lib/options/js/{0.14.9 → 0.15.0}/purge-cache.js
RENAMED
@@ -16,7 +16,7 @@ function openDeleteConvertedFilesPopup() {
|
|
16 |
function purgeCache(onlyPng) {
|
17 |
var data = {
|
18 |
'action': 'webpexpress_purge_cache',
|
19 |
-
'nonce' : window.
|
20 |
'only-png': onlyPng
|
21 |
};
|
22 |
jQuery.post(ajaxurl, data, function(response) {
|
16 |
function purgeCache(onlyPng) {
|
17 |
var data = {
|
18 |
'action': 'webpexpress_purge_cache',
|
19 |
+
'nonce' : window.webpExpress['ajax-nonces']['purge-cache'],
|
20 |
'only-png': onlyPng
|
21 |
};
|
22 |
jQuery.post(ajaxurl, data, function(response) {
|
lib/options/js/0.15.0/self-test.js
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
if (typeof WebPExpress == 'undefined') {
|
2 |
+
WebPExpress = {};
|
3 |
+
}
|
4 |
+
|
5 |
+
WebPExpress.SelfTest = {
|
6 |
+
'clear': function() {
|
7 |
+
document.getElementById('webpexpress_test_redirection_content').innerHTML = '';
|
8 |
+
},
|
9 |
+
'write': function(html) {
|
10 |
+
//var el = document.getElementById('webpexpress_test_redirection_content');
|
11 |
+
//el.innerHTML += html;
|
12 |
+
var el = document.createElement('div');
|
13 |
+
el.innerHTML = html;
|
14 |
+
document.getElementById('webpexpress_test_redirection_content').appendChild(el);
|
15 |
+
},
|
16 |
+
'simpleMdToHtml': function(line) {
|
17 |
+
//return s.replace(/./gm, function(s) {
|
18 |
+
|
19 |
+
|
20 |
+
if (line.substr(0, 3) == '```') {
|
21 |
+
return '<pre>' + line.replace(/\`/gm, '') + '</pre>';
|
22 |
+
}
|
23 |
+
|
24 |
+
// Bold with inline attributtes (ie: "hi **bold**{: .red}")
|
25 |
+
line = line.replace(/\*\*([^\*]+)\*\*\{:\s([^}]+)\}/gm, function(s, g1, g2) {
|
26 |
+
// g2 is the inline attributes.
|
27 |
+
// right now we only support classes, and only ONE class.
|
28 |
+
// so it is easy.
|
29 |
+
var className = g2.substr(1);
|
30 |
+
|
31 |
+
return '<b class="' + className + '">' + g1 + '</b>';
|
32 |
+
//return '<b>' + s.substr(2, s.length - 4) + '</b>';
|
33 |
+
});
|
34 |
+
|
35 |
+
// Bold
|
36 |
+
line = line.replace(/(\*\*[^\*]+\*\*)/gm, function(s) {
|
37 |
+
return '<b>' + s.substr(2, s.length - 4) + '</b>';
|
38 |
+
});
|
39 |
+
|
40 |
+
// Italic
|
41 |
+
line = line.replace(/(\*[^\*]+\*)/gm, function(s) {
|
42 |
+
return '<i>' + s.substr(1, s.length - 2) + '</i>';
|
43 |
+
});
|
44 |
+
|
45 |
+
// Headline
|
46 |
+
if (line.substr(0, 2) == '# ') {
|
47 |
+
line = '<h1>' + line.substr(2) + '</h1>';
|
48 |
+
}
|
49 |
+
if (line.substr(0, 3) == '## ') {
|
50 |
+
line = '<h2>' + line.substr(3) + '</h2>';
|
51 |
+
}
|
52 |
+
if (line.substr(0, 4) == '### ') {
|
53 |
+
line = '<h3>' + line.substr(4) + '</h3>';
|
54 |
+
}
|
55 |
+
if (line.substr(0, 5) == '#### ') {
|
56 |
+
line = '<h4>' + line.substr(5) + '</h4>';
|
57 |
+
}
|
58 |
+
|
59 |
+
// Empty line
|
60 |
+
if (line == '') {
|
61 |
+
line = '<br>';
|
62 |
+
}
|
63 |
+
|
64 |
+
|
65 |
+
// "ok!" green
|
66 |
+
line = line.replace(/ok\!/gmi, function(s) {
|
67 |
+
return '<span style="color:green; font-weight: bold;">ok</span>';
|
68 |
+
});
|
69 |
+
|
70 |
+
// "great" green
|
71 |
+
/*
|
72 |
+
line = line.replace(/great/gmi, function(s) {
|
73 |
+
return '<span style="color:green; font-weight: bold;">' + s + '</span>';
|
74 |
+
});*/
|
75 |
+
|
76 |
+
// "failed" red
|
77 |
+
line = line.replace(/failed/gmi, function(s) {
|
78 |
+
return '<span style="color:red; font-weight:bold">FAILED</span>';
|
79 |
+
});
|
80 |
+
|
81 |
+
return '<div class="webpexpress md">' + line + '</div>';
|
82 |
+
|
83 |
+
},
|
84 |
+
'responseHandler': function(response) {
|
85 |
+
if ((typeof response == 'object') && (response['success'] == false)) {
|
86 |
+
html = '<h1>Error</h1>';
|
87 |
+
if (response['data'] && ((typeof response['data']) == 'string')) {
|
88 |
+
html += webpexpress_escapeHTML(response['data']);
|
89 |
+
}
|
90 |
+
WebPExpress.SelfTest.write(html);
|
91 |
+
document.getElementById('bulkconvertcontent').innerHTML = html;
|
92 |
+
return;
|
93 |
+
}
|
94 |
+
|
95 |
+
var responseObj = JSON.parse(response);
|
96 |
+
var result = responseObj['result'];
|
97 |
+
if (typeof result == 'string') {
|
98 |
+
result = [result];
|
99 |
+
}
|
100 |
+
|
101 |
+
for (var i=0; i<result.length; i++) {
|
102 |
+
var line = result[i];
|
103 |
+
if (typeof line != 'string') {
|
104 |
+
continue;
|
105 |
+
}
|
106 |
+
line = webpexpress_escapeHTML(line);
|
107 |
+
line = WebPExpress.SelfTest.simpleMdToHtml(line);
|
108 |
+
|
109 |
+
WebPExpress.SelfTest.write(line);
|
110 |
+
}
|
111 |
+
//result = result.join('<br>');
|
112 |
+
|
113 |
+
|
114 |
+
|
115 |
+
var next = responseObj['next'];
|
116 |
+
if (next == 'done') {
|
117 |
+
//WebPExpress.SelfTest.write('<br>done');
|
118 |
+
} else if (next == 'break') {
|
119 |
+
WebPExpress.SelfTest.write('breaking');
|
120 |
+
} else {
|
121 |
+
WebPExpress.SelfTest.runTest(next);
|
122 |
+
}
|
123 |
+
|
124 |
+
},
|
125 |
+
'runTest': function(testId) {
|
126 |
+
var data = {
|
127 |
+
'action': 'webpexpress_self_test',
|
128 |
+
'testId': testId,
|
129 |
+
'nonce' : window.webpExpress['ajax-nonces']['self-test'],
|
130 |
+
};
|
131 |
+
|
132 |
+
jQuery.ajax({
|
133 |
+
method: 'POST',
|
134 |
+
url: ajaxurl,
|
135 |
+
data: data,
|
136 |
+
success: WebPExpress.SelfTest.responseHandler,
|
137 |
+
error: function() {
|
138 |
+
WebPExpress.SelfTest.write('PHP error. Check your debug.log for more info (make sure debugging is enabled)');
|
139 |
+
},
|
140 |
+
});
|
141 |
+
},
|
142 |
+
'openPopup': function(testId) {
|
143 |
+
WebPExpress.SelfTest.clear();
|
144 |
+
var w = Math.min(1000, Math.max(200, document.documentElement.clientWidth - 100));
|
145 |
+
var h = Math.max(250, document.documentElement.clientHeight - 80);
|
146 |
+
|
147 |
+
var title = 'Self testing';
|
148 |
+
if (testId == 'redirectToExisting') {
|
149 |
+
title = 'Testing redirection to existing webp';
|
150 |
+
}
|
151 |
+
tb_show(title, '#TB_inline?inlineId=webpexpress_test_redirection_popup&width=' + w + '&height=' + h);
|
152 |
+
|
153 |
+
WebPExpress.SelfTest.runTest(testId);
|
154 |
+
|
155 |
+
}
|
156 |
+
}
|
lib/options/js/{0.14.9 → 0.15.0}/sortable.min.js
RENAMED
File without changes
|
lib/options/js/{0.14.9 → 0.15.0}/test-convert.js
RENAMED
@@ -1,4 +1,3 @@
|
|
1 |
-
|
2 |
function openTestConvertPopup(converterId) {
|
3 |
var html = '<div id="tc_conversion_options">options</div><div><div id="tc_conversion_result"><h2>Result</h2>wait...</div></div>'
|
4 |
document.getElementById('tc_content').innerHTML = html;
|
@@ -130,7 +129,7 @@ function runTestConversion() {
|
|
130 |
|
131 |
var data = {
|
132 |
'action': 'convert_file',
|
133 |
-
'nonce': window.
|
134 |
'filename': window.webpExpressPaths['filePaths']['webpExpressRoot'] + '/test/' + elTxt('image'),
|
135 |
"converter": elTxt("converter"),
|
136 |
'config-overrides': JSON.stringify(configOverrides)
|
@@ -239,14 +238,21 @@ function convertResponseCallback(response){
|
|
239 |
var srcUrl = '/' + window.webpExpressPaths['urls']['webpExpressRoot'] + '/test/' + filename;
|
240 |
//html += '<img src="/' + srcUrl + '" style="width:100%">';
|
241 |
|
|
|
|
|
|
|
|
|
242 |
var webpUrl = '/' + window.webpExpressPaths['urls']['content'] +
|
243 |
'/webp-express/webp-images/doc-root/' +
|
244 |
window.webpExpressPaths['filePaths']['pluginRelToDocRoot'] + '/' +
|
245 |
'webp-express/' +
|
246 |
'test/' +
|
247 |
filename + '.webp';
|
|
|
248 |
//html += '<img src="' + webpUrl + '" style="width:100%">';
|
249 |
|
|
|
|
|
250 |
html += '<div class="cd-image-container">';
|
251 |
html += ' <div class="cd-image-label webp">WebP: ' + webpSizeStr + '</div>';
|
252 |
html += ' <div class="cd-image-label original">' + (filename.toLowerCase().indexOf('png') > 0 ? 'PNG' : 'JPEG') + ': ' + orgSizeStr + '</div>';
|
|
|
1 |
function openTestConvertPopup(converterId) {
|
2 |
var html = '<div id="tc_conversion_options">options</div><div><div id="tc_conversion_result"><h2>Result</h2>wait...</div></div>'
|
3 |
document.getElementById('tc_content').innerHTML = html;
|
129 |
|
130 |
var data = {
|
131 |
'action': 'convert_file',
|
132 |
+
'nonce': window.webpExpress['ajax-nonces']['convert'],
|
133 |
'filename': window.webpExpressPaths['filePaths']['webpExpressRoot'] + '/test/' + elTxt('image'),
|
134 |
"converter": elTxt("converter"),
|
135 |
'config-overrides': JSON.stringify(configOverrides)
|
238 |
var srcUrl = '/' + window.webpExpressPaths['urls']['webpExpressRoot'] + '/test/' + filename;
|
239 |
//html += '<img src="/' + srcUrl + '" style="width:100%">';
|
240 |
|
241 |
+
|
242 |
+
|
243 |
+
// TODO: THIS DOES NOT WORK. NEEDS ATTENTION!
|
244 |
+
/*
|
245 |
var webpUrl = '/' + window.webpExpressPaths['urls']['content'] +
|
246 |
'/webp-express/webp-images/doc-root/' +
|
247 |
window.webpExpressPaths['filePaths']['pluginRelToDocRoot'] + '/' +
|
248 |
'webp-express/' +
|
249 |
'test/' +
|
250 |
filename + '.webp';
|
251 |
+
*/
|
252 |
//html += '<img src="' + webpUrl + '" style="width:100%">';
|
253 |
|
254 |
+
var webpUrl = result['destination-url'];
|
255 |
+
|
256 |
html += '<div class="cd-image-container">';
|
257 |
html += ' <div class="cd-image-label webp">WebP: ' + webpSizeStr + '</div>';
|
258 |
html += ' <div class="cd-image-label original">' + (filename.toLowerCase().indexOf('png') > 0 ? 'PNG' : 'JPEG') + ': ' + orgSizeStr + '</div>';
|
lib/options/js/{0.14.9 → 0.15.0}/whitelist.js
RENAMED
File without changes
|
lib/options/options/conversion-options/jpeg.inc
CHANGED
@@ -19,7 +19,7 @@ png
|
|
19 |
//$canDetectQuality = false;
|
20 |
?>
|
21 |
|
22 |
-
<tr>
|
23 |
<th scope="row" colspan=1>
|
24 |
Jpeg options
|
25 |
<?php echo helpIcon(
|
19 |
//$canDetectQuality = false;
|
20 |
?>
|
21 |
|
22 |
+
<tr id="jpeg_row" class="toggler effect-opacity">
|
23 |
<th scope="row" colspan=1>
|
24 |
Jpeg options
|
25 |
<?php echo helpIcon(
|
lib/options/options/general/destination-extension.inc
CHANGED
@@ -30,7 +30,8 @@
|
|
30 |
'or <a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>, set this option to Set"</p>' .
|
31 |
(($config['operation-mode'] == 'cdn-friendly') ? '<p>In this mode, the webp files will be stored in the same folder as the originals, except for images that are not inside the uploads folder (these are stored in wp-content/webp-express/webp-images/doc-root).</p>' : '') .
|
32 |
'<p>Changing this option will cause existing webp images to be renamed (only those in the upload folder, and only those that has a ' .
|
33 |
-
'corresponding source image)</p>'
|
|
|
34 |
);
|
35 |
}
|
36 |
?>
|
30 |
'or <a target="_blank" href="https://wordpress.org/plugins/shortpixel-image-optimiser/">Shortpixel</a>, set this option to Set"</p>' .
|
31 |
(($config['operation-mode'] == 'cdn-friendly') ? '<p>In this mode, the webp files will be stored in the same folder as the originals, except for images that are not inside the uploads folder (these are stored in wp-content/webp-express/webp-images/doc-root).</p>' : '') .
|
32 |
'<p>Changing this option will cause existing webp images to be renamed (only those in the upload folder, and only those that has a ' .
|
33 |
+
'corresponding source image)</p>' .
|
34 |
+
'<p>Note that this option only applies to the webp images stored in the uploads folder (mingled).</p>'
|
35 |
);
|
36 |
}
|
37 |
?>
|
lib/options/options/general/destination-structure.inc
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
<tr id="destination_structure_row">
|
3 |
+
<th scope="row">Destination structure<?php
|
4 |
+
echo helpIcon(
|
5 |
+
'<p>This setting determines how the converted files are structured within the folder that WebP Express ' .
|
6 |
+
'uses for storing webp images</p>' .
|
7 |
+
'<p><span style="text-decoration:underline">"document root"</span><br>' .
|
8 |
+
'When "document root" is selected, the webp images will be stored in:<br>' .
|
9 |
+
'<span style="white-space:pre-wrap;font-family:monospace">[cache root]/doc-root/[relative path of source image, from document root].</span><br>' .
|
10 |
+
'</p>' .
|
11 |
+
'<p><span style="text-decoration:underline">"image roots"</span><br>' .
|
12 |
+
'A Wordpress site has images stored in different locations. Uploaded files are for example stored in the <i>uploads</i> folder, which is ' .
|
13 |
+
'usually - but not always - located in the "wp-content" folder. I call the uploads folder an "image root". Other roots are: ' .
|
14 |
+
'"themes", "plugins", "wp-content" and "index". When the "image roots" setting is selected, the webp files ' .
|
15 |
+
'are stored in a structure that mirrors the relative path of the source image within its image root. ' .
|
16 |
+
'For "uploads", that location is:<br>' .
|
17 |
+
'<span style="white-space:pre-wrap;font-family:monospace">[cache root]/uploads/[relative path of source image, from uploads root].</span><br>' .
|
18 |
+
'More generally we have:<br>' .
|
19 |
+
'<span style="white-space:pre-wrap;font-family:monospace"">[cache root]/[image root]/[relative path of source image, from its image root].</span><br>' .
|
20 |
+
'</p>' .
|
21 |
+
'<p><span style="text-decoration:underline">Which option is best?</span><br>' .
|
22 |
+
'Well, in most cases it does not matter. However, as the "image roots" option is new, I would recommend staying with the ' .
|
23 |
+
'"document root" setting. On Nginx, I recommend "document root", as it requires fewer rewrite rules.</p>' .
|
24 |
+
'<p>The "image roots" option was added to make WebP Express work on systems where the document root is set up incorrectly or ' .
|
25 |
+
'is outside opendir restriction or an image root is located outside document root. WebP Express automatically detects this and will not allow you to select "document root" in those cases.</p>'
|
26 |
+
);
|
27 |
+
?></th>
|
28 |
+
<td>
|
29 |
+
<select name="destination-structure" id="destination_structure">
|
30 |
+
<?php
|
31 |
+
webpexpress_selectBoxOptions($config['destination-structure'], [
|
32 |
+
'doc-root' => 'Document root',
|
33 |
+
'image-roots' => 'Image roots',
|
34 |
+
]);
|
35 |
+
?>
|
36 |
+
</select>
|
37 |
+
</td>
|
38 |
+
</tr>
|
lib/options/options/general/general.inc
CHANGED
@@ -1,9 +1,16 @@
|
|
1 |
<fieldset class="block">
|
|
|
|
|
|
|
2 |
<h3>General</h3>
|
|
|
3 |
<!--<div><i>The options here affects the rules created in the .htaccess. <?php echo helpIcon('And so does some other options. If "Redirect directly to converted image" is set, the "Destination folder" and "File Extension" and "Caching" options will be used'); ?></i></div>-->
|
4 |
<table class="form-table">
|
5 |
<tbody>
|
6 |
<?php
|
|
|
|
|
|
|
7 |
include_once 'image-types.inc';
|
8 |
|
9 |
if (($config['operation-mode'] != 'no-conversion')) {
|
@@ -11,6 +18,10 @@
|
|
11 |
}
|
12 |
include_once 'destination-extension.inc';
|
13 |
|
|
|
|
|
|
|
|
|
14 |
// TODO:
|
15 |
// Perhaps also show cache control in cdn-friendly mode?
|
16 |
if (($config['operation-mode'] == 'tweaked') || ($config['operation-mode'] == 'varied-image-responses')) {
|
1 |
<fieldset class="block">
|
2 |
+
<button onclick="WebPExpress.SelfTest.openPopup('allInfo')" class="button button-secondary" type="button" style="position: absolute; right: 15px; top:15px">
|
3 |
+
System info
|
4 |
+
</button>
|
5 |
<h3>General</h3>
|
6 |
+
|
7 |
<!--<div><i>The options here affects the rules created in the .htaccess. <?php echo helpIcon('And so does some other options. If "Redirect directly to converted image" is set, the "Destination folder" and "File Extension" and "Caching" options will be used'); ?></i></div>-->
|
8 |
<table class="form-table">
|
9 |
<tbody>
|
10 |
<?php
|
11 |
+
if (($config['operation-mode'] != 'no-conversion')) {
|
12 |
+
include_once 'scope.inc';
|
13 |
+
}
|
14 |
include_once 'image-types.inc';
|
15 |
|
16 |
if (($config['operation-mode'] != 'no-conversion')) {
|
18 |
}
|
19 |
include_once 'destination-extension.inc';
|
20 |
|
21 |
+
if (($config['operation-mode'] != 'no-conversion')) {
|
22 |
+
include_once 'destination-structure.inc';
|
23 |
+
}
|
24 |
+
|
25 |
// TODO:
|
26 |
// Perhaps also show cache control in cdn-friendly mode?
|
27 |
if (($config['operation-mode'] == 'tweaked') || ($config['operation-mode'] == 'varied-image-responses')) {
|
lib/options/options/general/image-types.inc
CHANGED
@@ -33,6 +33,7 @@ $imageTypes = $config['image-types'];
|
|
33 |
echo '<select name="image-types" id="image_types">';
|
34 |
echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>None! (disable)</option>';
|
35 |
echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only jpegs</option>';
|
|
|
36 |
echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Both jpegs and pngs</option>';
|
37 |
echo '</select>';
|
38 |
|
33 |
echo '<select name="image-types" id="image_types">';
|
34 |
echo '<option value="0"' . ($imageTypes == 0 ? ' selected' : '') . '>None! (disable)</option>';
|
35 |
echo '<option value="1"' . ($imageTypes == 1 ? ' selected' : '') . '>Only jpegs</option>';
|
36 |
+
echo '<option value="2"' . ($imageTypes == 2 ? ' selected' : '') . '>Only pngs</option>';
|
37 |
echo '<option value="3"' . ($imageTypes == 3 ? ' selected' : '') . '>Both jpegs and pngs</option>';
|
38 |
echo '</select>';
|
39 |
|
lib/options/options/general/scope.inc
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<tr>
|
2 |
+
<th scope="row">Scope<?php
|
3 |
+
echo helpIcon(
|
4 |
+
'<p>This setting determines which folders WebP Express is operational in. ' .
|
5 |
+
'If for example "Uploads only" is selected, WebP Express will only convert the upload images and only put ' .
|
6 |
+
'an <i>.htaccess</i> file in the <i>uploads</i> folder (if needed). Also, <i>Alter HTML</i> will limit itself to that area.' .
|
7 |
+
'</p>' .
|
8 |
+
'<p>The "All content" setting will work on <i>uploads</i>, <i>themes</i>, <i>plugins</i> - anything in the "wp-content" ' .
|
9 |
+
'(or whatever it has been renamed to). It will work on uploads, even if the uploads folder has been ' .
|
10 |
+
'configured to reside outside of wp-content - and on <i>plugins<i>, even if plugins has been moved.</p>'
|
11 |
+
);
|
12 |
+
?></th>
|
13 |
+
<td>
|
14 |
+
<select name="scope" id="scope">
|
15 |
+
<?php
|
16 |
+
/*webpexpress_selectBoxOptions($config['destination-structure'], [
|
17 |
+
'doc-root' => 'Document root',
|
18 |
+
'image-roots' => 'Image roots',
|
19 |
+
]);
|
20 |
+
*/
|
21 |
+
$imageTypes = $config['scope'];
|
22 |
+
sort($imageTypes, SORT_STRING);
|
23 |
+
$imageTypesString = implode(',', $imageTypes);
|
24 |
+
|
25 |
+
$options = [
|
26 |
+
['uploads', 'Uploads only'],
|
27 |
+
['themes', 'Themes only'],
|
28 |
+
['themes,uploads', 'Uploads and themes'],
|
29 |
+
['plugins,themes,uploads,wp-content', 'All content'],
|
30 |
+
['index,plugins,themes,uploads,wp-content', 'Everything (including wp-admin)'],
|
31 |
+
];
|
32 |
+
|
33 |
+
$otherValidOptions = ['index', 'wp-content'];
|
34 |
+
|
35 |
+
if (in_array($imageTypesString, $otherValidOptions)) {
|
36 |
+
$options[] = [$imageTypesString, $imageTypesString];
|
37 |
+
}
|
38 |
+
|
39 |
+
$hasSelected = false;
|
40 |
+
foreach ($options as list($optionId, $optionName)) {
|
41 |
+
$selected = ($imageTypesString == $optionId);
|
42 |
+
if ($selected) {
|
43 |
+
$hasSelected = true;
|
44 |
+
}
|
45 |
+
echo '<option value="' . $optionId . '"' . ($selected ? ' selected' : '') . '>' . $optionName . '</option>';
|
46 |
+
}
|
47 |
+
|
48 |
+
if (!$hasSelected) {
|
49 |
+
// Non-default option is "selected".
|
50 |
+
// This can be done in migrate11.php or user could have edited config.php manually.
|
51 |
+
// PS: We can rest assure that scope is sanitized - any invalid rootId is filtered out in Config::fix.
|
52 |
+
echo '<option value="' . $imageTypesString . '" selected>' .
|
53 |
+
'Custom: ' . implode(', ', explode(',', $imageTypesString)) .
|
54 |
+
'</option>';
|
55 |
+
}
|
56 |
+
?>
|
57 |
+
</select>
|
58 |
+
</td>
|
59 |
+
</tr>
|
lib/options/options/redirection-rules/enable-redirection-to-converter.inc
CHANGED
@@ -18,5 +18,8 @@
|
|
18 |
value="true"
|
19 |
type="checkbox"
|
20 |
>
|
|
|
|
|
|
|
21 |
</td>
|
22 |
</tr>
|
18 |
value="true"
|
19 |
type="checkbox"
|
20 |
>
|
21 |
+
<button style="margin-left: 30px;" onclick="WebPExpress.SelfTest.openPopup('redirectToConverter')" class="button button-secondary" type="button">
|
22 |
+
Live test
|
23 |
+
</button>
|
24 |
</td>
|
25 |
</tr>
|
lib/options/options/redirection-rules/enable-redirection-to-webp-realizer.inc
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<tr>
|
2 |
<th scope="row">
|
3 |
-
Create webp files upon request
|
4 |
<?php echo helpIcon(
|
5 |
'<p>Enabling this option will add lines in the .htaccess which redirects requests for non-existing webp-files to ' .
|
6 |
'the converter script (webp-realizer.php). ' .
|
@@ -25,5 +25,8 @@
|
|
25 |
value="true"
|
26 |
type="checkbox"
|
27 |
>
|
|
|
|
|
|
|
28 |
</td>
|
29 |
</tr>
|
1 |
<tr>
|
2 |
<th scope="row">
|
3 |
+
Create webp files upon request?
|
4 |
<?php echo helpIcon(
|
5 |
'<p>Enabling this option will add lines in the .htaccess which redirects requests for non-existing webp-files to ' .
|
6 |
'the converter script (webp-realizer.php). ' .
|
25 |
value="true"
|
26 |
type="checkbox"
|
27 |
>
|
28 |
+
<button style="margin-left: 30px;" onclick="WebPExpress.SelfTest.openPopup('redirectToWebPRealizer')" class="button button-secondary" type="button">
|
29 |
+
Live test
|
30 |
+
</button>
|
31 |
</td>
|
32 |
</tr>
|
lib/options/options/redirection-rules/redirect-to-existing.inc
CHANGED
@@ -24,6 +24,10 @@
|
|
24 |
echo ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '')
|
25 |
?> >
|
26 |
|
|
|
|
|
|
|
|
|
27 |
<?php
|
28 |
/*if ($config['operation-mode'] == 'no-conversion') {
|
29 |
// Cache control header
|
24 |
echo ($config['redirect-to-existing-in-htaccess'] ? 'checked="checked"' : '')
|
25 |
?> >
|
26 |
|
27 |
+
<button style="margin-left: 30px;" onclick="WebPExpress.SelfTest.openPopup('redirectToExisting')" class="button button-secondary" type="button">
|
28 |
+
Live test
|
29 |
+
</button>
|
30 |
+
|
31 |
<?php
|
32 |
/*if ($config['operation-mode'] == 'no-conversion') {
|
33 |
// Cache control header
|
lib/options/options/redirection-rules/redirection-rules.inc
CHANGED
@@ -1,14 +1,14 @@
|
|
1 |
<fieldset class="block">
|
2 |
-
|
3 |
<h2>Redirecting jpeg/png to existing webp (varied image response)</h2>
|
4 |
<p>
|
5 |
Enabling this adds rules to the <i>.htaccess</i> which internally redirects jpg/pngs to webp
|
6 |
and sets the <i>Vary:Accept</i> response header.
|
7 |
<i>Beware that special attention is needed if you are using a CDN (see FAQ).</i>
|
8 |
</p>
|
9 |
-
|
10 |
<h2><i>.htaccess</i> rules for webp generation</h2>
|
11 |
-
|
12 |
<h3>Redirection rules</h3>
|
13 |
<div><i>The options here affects the rules created in the .htaccess. <?php
|
14 |
echo helpIcon('Note: The general options also affects the rules.');
|
@@ -44,8 +44,14 @@
|
|
44 |
include_once 'enable-redirection-to-converter.inc';
|
45 |
include_once 'enable-redirection-to-webp-realizer.inc';
|
46 |
break;
|
|
|
|
|
|
|
47 |
}
|
48 |
?>
|
49 |
</tbody>
|
50 |
</table>
|
|
|
|
|
|
|
51 |
</fieldset>
|
1 |
<fieldset class="block">
|
2 |
+
<?php if ($config['operation-mode'] == 'no-conversion') : ?>
|
3 |
<h2>Redirecting jpeg/png to existing webp (varied image response)</h2>
|
4 |
<p>
|
5 |
Enabling this adds rules to the <i>.htaccess</i> which internally redirects jpg/pngs to webp
|
6 |
and sets the <i>Vary:Accept</i> response header.
|
7 |
<i>Beware that special attention is needed if you are using a CDN (see FAQ).</i>
|
8 |
</p>
|
9 |
+
<?php elseif ($config['operation-mode'] == 'cdn-friendly') : ?>
|
10 |
<h2><i>.htaccess</i> rules for webp generation</h2>
|
11 |
+
<?php else : ?>
|
12 |
<h3>Redirection rules</h3>
|
13 |
<div><i>The options here affects the rules created in the .htaccess. <?php
|
14 |
echo helpIcon('Note: The general options also affects the rules.');
|
44 |
include_once 'enable-redirection-to-converter.inc';
|
45 |
include_once 'enable-redirection-to-webp-realizer.inc';
|
46 |
break;
|
47 |
+
default:
|
48 |
+
echo 'Error: unknown operation mode! Try saving, it should fix it';
|
49 |
+
break;
|
50 |
}
|
51 |
?>
|
52 |
</tbody>
|
53 |
</table>
|
54 |
+
<div id="webpexpress_test_redirection_popup" style="display:none;">
|
55 |
+
<div id="webpexpress_test_redirection_content"></div>
|
56 |
+
</div>
|
57 |
</fieldset>
|
lib/options/page-messages.php
CHANGED
@@ -98,7 +98,7 @@ if (($config['operation-mode'] == 'cdn-friendly') && !$config['alter-html']['ena
|
|
98 |
if ($webpEnabled) {
|
99 |
Messenger::printMessage(
|
100 |
'info',
|
101 |
-
'You should consider enabling Alter HTML. This is not
|
102 |
'However, it is a good idea because currently <i>Cache Enabler</i> does not replace as many URLs as WebP Express (ie ' .
|
103 |
'background images in inline styles)'
|
104 |
);
|
@@ -169,7 +169,7 @@ if (Config::isConfigFileThereAndOk() ) { // && PlatformInfo::definitelyGotModEnv
|
|
169 |
"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>" .
|
170 |
"Disabled <i>.htaccess</i> files is actually a good thing, both performance-wise and security-wise. <br> " .
|
171 |
"But it means you will have to insert the following rules into your apache configuration manually:" .
|
172 |
-
"<pre>" . htmlentities(print_r(Config::
|
173 |
);
|
174 |
}
|
175 |
}*/
|
98 |
if ($webpEnabled) {
|
99 |
Messenger::printMessage(
|
100 |
'info',
|
101 |
+
'You should consider enabling Alter HTML. This is not necessary, as you have <i>Cache Enabler</i> enabled, which alters HTML. ' .
|
102 |
'However, it is a good idea because currently <i>Cache Enabler</i> does not replace as many URLs as WebP Express (ie ' .
|
103 |
'background images in inline styles)'
|
104 |
);
|
169 |
"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>" .
|
170 |
"Disabled <i>.htaccess</i> files is actually a good thing, both performance-wise and security-wise. <br> " .
|
171 |
"But it means you will have to insert the following rules into your apache configuration manually:" .
|
172 |
+
"<pre>" . htmlentities(print_r(Config::hmmm(), true)) . "</pre>"
|
173 |
);
|
174 |
}
|
175 |
}*/
|
lib/options/page.php
CHANGED
@@ -121,7 +121,11 @@ function helpIcon($text, $customClass = '') {
|
|
121 |
if (strlen($text) > 150) {
|
122 |
if (strlen($text) > 300) {
|
123 |
if (strlen($text) > 500) {
|
124 |
-
$
|
|
|
|
|
|
|
|
|
125 |
} else {
|
126 |
$className = 'wider';
|
127 |
}
|
121 |
if (strlen($text) > 150) {
|
122 |
if (strlen($text) > 300) {
|
123 |
if (strlen($text) > 500) {
|
124 |
+
if (strlen($text) > 1000) {
|
125 |
+
$className = 'widest';
|
126 |
+
} else {
|
127 |
+
$className = 'even-wider';
|
128 |
+
}
|
129 |
} else {
|
130 |
$className = 'wider';
|
131 |
}
|
lib/options/submit.php
CHANGED
@@ -9,13 +9,18 @@ use \WebPExpress\ConvertersHelper;
|
|
9 |
use \WebPExpress\DismissableMessages;
|
10 |
use \WebPExpress\HTAccess;
|
11 |
use \WebPExpress\Messenger;
|
|
|
12 |
use \WebPExpress\Paths;
|
13 |
|
|
|
14 |
|
15 |
check_admin_referer('webpexpress-save-settings-nonce');
|
16 |
|
17 |
DismissableMessages::dismissMessage('0.14.0/say-hello-to-vips');
|
18 |
|
|
|
|
|
|
|
19 |
|
20 |
/*
|
21 |
--------------------------------
|
@@ -116,6 +121,21 @@ function webpexpress_getSanitizedQuality($keyInPOST, $fallback = 75) {
|
|
116 |
return max(0, min($q, 100));
|
117 |
}
|
118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
|
120 |
/**
|
121 |
* Get sanitized whitelist
|
@@ -164,7 +184,7 @@ function webpexpress_getSanitizedWhitelist() {
|
|
164 |
*/
|
165 |
function webpexpress_getSanitizedConverters() {
|
166 |
$convertersPosted = (isset($_POST['converters']) ? $_POST['converters'] : '[]');
|
167 |
-
$convertersPosted = json_decode(wp_unslash($convertersPosted), true); // holy moly! - https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
|
168 |
|
169 |
$convertersSanitized = [];
|
170 |
|
@@ -305,8 +325,10 @@ $sanitized = [
|
|
305 |
'image-types' => intval(webpexpress_getSanitizedChooseFromSet('image-types', '3', [
|
306 |
'0',
|
307 |
'1',
|
|
|
308 |
'3'
|
309 |
])),
|
|
|
310 |
'destination-folder' => webpexpress_getSanitizedChooseFromSet('destination-folder', 'separate', [
|
311 |
'separate',
|
312 |
'mingled',
|
@@ -315,6 +337,10 @@ $sanitized = [
|
|
315 |
'append',
|
316 |
'set',
|
317 |
]),
|
|
|
|
|
|
|
|
|
318 |
'cache-control' => webpexpress_getSanitizedChooseFromSet('cache-control', 'no-header', [
|
319 |
'no-header',
|
320 |
'set',
|
@@ -418,6 +444,9 @@ $sanitized = [
|
|
418 |
|
419 |
];
|
420 |
|
|
|
|
|
|
|
421 |
|
422 |
/*
|
423 |
------------------------------------------------------
|
@@ -435,7 +464,7 @@ $oldConfig = $config;
|
|
435 |
$config = array_merge($config, [
|
436 |
'operation-mode' => $sanitized['operation-mode'],
|
437 |
|
438 |
-
|
439 |
'image-types' => $sanitized['image-types'],
|
440 |
'forward-query-string' => true,
|
441 |
]);
|
@@ -580,6 +609,7 @@ if ($sanitized['operation-mode'] != 'no-conversion') {
|
|
580 |
}
|
581 |
}
|
582 |
|
|
|
583 |
|
584 |
switch ($sanitized['operation-mode']) {
|
585 |
case 'varied-image-responses':
|
@@ -637,6 +667,18 @@ if ($sanitized['force'] || HTAccess::doesRewriteRulesNeedUpdate($config)) {
|
|
637 |
}
|
638 |
|
639 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
640 |
// SAVE!
|
641 |
// -----
|
642 |
$result = Config::saveConfigurationAndHTAccess($config, $sanitized['force']);
|
@@ -660,18 +702,26 @@ if (!$result['saved-both-config']) {
|
|
660 |
|
661 |
}
|
662 |
} else {
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
668 |
} else {
|
669 |
-
if ($
|
670 |
-
$
|
671 |
-
$
|
672 |
} else {
|
673 |
-
$
|
674 |
-
$
|
675 |
}
|
676 |
}
|
677 |
|
@@ -680,32 +730,31 @@ if (!$result['saved-both-config']) {
|
|
680 |
if ($numFilesMoved == 0) {
|
681 |
Messenger::addMessage(
|
682 |
'notice',
|
683 |
-
'No cached webp files needed to be ' . $
|
684 |
);
|
685 |
|
686 |
} else {
|
687 |
Messenger::addMessage(
|
688 |
'success',
|
689 |
-
'The webp files was ' . $
|
690 |
);
|
691 |
}
|
692 |
} else {
|
693 |
if ($numFilesMoved == 0) {
|
694 |
Messenger::addMessage(
|
695 |
'warning',
|
696 |
-
'No webp files could not be ' . $
|
697 |
);
|
698 |
} else {
|
699 |
Messenger::addMessage(
|
700 |
'warning',
|
701 |
-
'Some webp files could not be ' . $
|
702 |
);
|
703 |
|
704 |
}
|
705 |
}
|
706 |
}
|
707 |
|
708 |
-
|
709 |
if (!$result['rules-needed-update']) {
|
710 |
Messenger::addMessage(
|
711 |
'success',
|
@@ -713,6 +762,49 @@ if (!$result['saved-both-config']) {
|
|
713 |
);
|
714 |
} else {
|
715 |
$rulesResult = $result['htaccess-result'];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
716 |
/*
|
717 |
'mainResult' // 'index', 'wp-content' or 'failed'
|
718 |
'minRequired' // 'index' or 'wp-content'
|
@@ -725,6 +817,7 @@ if (!$result['saved-both-config']) {
|
|
725 |
'uploadFailed' // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
|
726 |
'uploadFailedBadly' // true if plugin failed AND it seems we have rewrite rules there
|
727 |
*/
|
|
|
728 |
$mainResult = $rulesResult['mainResult'];
|
729 |
$rules = $rulesResult['rules'];
|
730 |
|
@@ -813,7 +906,7 @@ if (!$result['saved-both-config']) {
|
|
813 |
'Please grant write permmissions to you uploads folder. Otherwise uploaded mages will not be converted to webp'
|
814 |
);
|
815 |
}
|
816 |
-
}
|
817 |
}
|
818 |
}
|
819 |
|
9 |
use \WebPExpress\DismissableMessages;
|
10 |
use \WebPExpress\HTAccess;
|
11 |
use \WebPExpress\Messenger;
|
12 |
+
use \WebPExpress\PathHelper;
|
13 |
use \WebPExpress\Paths;
|
14 |
|
15 |
+
// TODO: Move this code to a class
|
16 |
|
17 |
check_admin_referer('webpexpress-save-settings-nonce');
|
18 |
|
19 |
DismissableMessages::dismissMessage('0.14.0/say-hello-to-vips');
|
20 |
|
21 |
+
DismissableMessages::dismissMessage('0.15.0/new-scope-setting-no-uploads');
|
22 |
+
DismissableMessages::dismissMessage('0.15.0/new-scope-setting-index');
|
23 |
+
DismissableMessages::dismissMessage('0.15.0/new-scope-setting-content');
|
24 |
|
25 |
/*
|
26 |
--------------------------------
|
121 |
return max(0, min($q, 100));
|
122 |
}
|
123 |
|
124 |
+
function webpexpress_getSanitizedScope() {
|
125 |
+
$scopeText = webpexpress_getSanitizedText('scope');
|
126 |
+
if ($scopeText == '') {
|
127 |
+
$scopeText = 'uploads';
|
128 |
+
}
|
129 |
+
$scope = explode(',', $scopeText);
|
130 |
+
$allowed = Paths::getImageRootIds();
|
131 |
+
$result = [];
|
132 |
+
foreach ($scope as $imageRootId) {
|
133 |
+
if (in_array($imageRootId, $allowed)) {
|
134 |
+
$result[] = $imageRootId;
|
135 |
+
}
|
136 |
+
}
|
137 |
+
return $result;
|
138 |
+
}
|
139 |
|
140 |
/**
|
141 |
* Get sanitized whitelist
|
184 |
*/
|
185 |
function webpexpress_getSanitizedConverters() {
|
186 |
$convertersPosted = (isset($_POST['converters']) ? $_POST['converters'] : '[]');
|
187 |
+
$convertersPosted = json_decode(wp_unslash($convertersPosted), true); // holy moly! Wordpress automatically adds slashes to the global POST vars- https://stackoverflow.com/questions/2496455/why-are-post-variables-getting-escaped-in-php
|
188 |
|
189 |
$convertersSanitized = [];
|
190 |
|
325 |
'image-types' => intval(webpexpress_getSanitizedChooseFromSet('image-types', '3', [
|
326 |
'0',
|
327 |
'1',
|
328 |
+
'2',
|
329 |
'3'
|
330 |
])),
|
331 |
+
'scope' => webpexpress_getSanitizedScope(),
|
332 |
'destination-folder' => webpexpress_getSanitizedChooseFromSet('destination-folder', 'separate', [
|
333 |
'separate',
|
334 |
'mingled',
|
337 |
'append',
|
338 |
'set',
|
339 |
]),
|
340 |
+
'destination-structure' => webpexpress_getSanitizedChooseFromSet('destination-structure', 'doc-root', [
|
341 |
+
'doc-root',
|
342 |
+
'image-roots',
|
343 |
+
]),
|
344 |
'cache-control' => webpexpress_getSanitizedChooseFromSet('cache-control', 'no-header', [
|
345 |
'no-header',
|
346 |
'set',
|
444 |
|
445 |
];
|
446 |
|
447 |
+
if (!Paths::canUseDocRootForRelPaths()) {
|
448 |
+
$sanitized['destination-structure'] = 'image-roots';
|
449 |
+
}
|
450 |
|
451 |
/*
|
452 |
------------------------------------------------------
|
464 |
$config = array_merge($config, [
|
465 |
'operation-mode' => $sanitized['operation-mode'],
|
466 |
|
467 |
+
'scope' => $sanitized['scope'],
|
468 |
'image-types' => $sanitized['image-types'],
|
469 |
'forward-query-string' => true,
|
470 |
]);
|
609 |
}
|
610 |
}
|
611 |
|
612 |
+
$config['destination-structure'] = $sanitized['destination-structure'];
|
613 |
|
614 |
switch ($sanitized['operation-mode']) {
|
615 |
case 'varied-image-responses':
|
667 |
}
|
668 |
|
669 |
|
670 |
+
$config['environment-when-config-was-saved'] = [
|
671 |
+
'doc-root-available' => PathHelper::isDocRootAvailable(),
|
672 |
+
'doc-root-resolvable' => PathHelper::isDocRootAvailableAndResolvable(),
|
673 |
+
'doc-root-usable-for-structuring' => Paths::canUseDocRootForRelPaths(),
|
674 |
+
'image-roots' => Paths::getImageRootsDef(),
|
675 |
+
'document-root' => null,
|
676 |
+
];
|
677 |
+
|
678 |
+
if (PathHelper::isDocRootAvailable()) {
|
679 |
+
$config['document-root'] = $_SERVER['DOCUMENT_ROOT'];
|
680 |
+
}
|
681 |
+
|
682 |
// SAVE!
|
683 |
// -----
|
684 |
$result = Config::saveConfigurationAndHTAccess($config, $sanitized['force']);
|
702 |
|
703 |
}
|
704 |
} else {
|
705 |
+
$changeFolder = ($config['destination-folder'] != $oldConfig['destination-folder']);
|
706 |
+
$changeExtension = ($config['destination-extension'] != $oldConfig['destination-extension']);
|
707 |
+
$changeStructure = ($config['destination-structure'] != $oldConfig['destination-structure']);
|
708 |
+
|
709 |
+
if ($changeFolder || $changeExtension || $changeStructure) {
|
710 |
+
|
711 |
+
$relocate = $changeFolder || $changeStructure;
|
712 |
+
$rename = $changeExtension;
|
713 |
+
|
714 |
+
$actionPastTense = '';
|
715 |
+
if ($rename && $relocate) {
|
716 |
+
$actionPastTense = 'relocated and renamed';
|
717 |
+
$actionPresentTense = 'relocate and rename';
|
718 |
} else {
|
719 |
+
if ($rename) {
|
720 |
+
$actionPastTense = 'renamed';
|
721 |
+
$actionPresentTense = 'rename';
|
722 |
} else {
|
723 |
+
$actionPastTense = 'relocated';
|
724 |
+
$actionPresentTense = 'relocate';
|
725 |
}
|
726 |
}
|
727 |
|
730 |
if ($numFilesMoved == 0) {
|
731 |
Messenger::addMessage(
|
732 |
'notice',
|
733 |
+
'No cached webp files needed to be ' . $actionPastTense
|
734 |
);
|
735 |
|
736 |
} else {
|
737 |
Messenger::addMessage(
|
738 |
'success',
|
739 |
+
'The webp files was ' . $actionPastTense . ' (' . $actionPastTense . ' ' . $numFilesMoved . ' images)'
|
740 |
);
|
741 |
}
|
742 |
} else {
|
743 |
if ($numFilesMoved == 0) {
|
744 |
Messenger::addMessage(
|
745 |
'warning',
|
746 |
+
'No webp files could not be ' . $actionPastTense . ' (failed to ' . $actionPresentTense . ' ' . $numFilesFailedMoving . ' images)'
|
747 |
);
|
748 |
} else {
|
749 |
Messenger::addMessage(
|
750 |
'warning',
|
751 |
+
'Some webp files could not be ' . $actionPastTense . ' (failed to ' . $actionPresentTense . ' ' . $numFilesFailedMoving . ' images, but successfully ' . $actionPastTense . ' ' . $numFilesMoved . ' images)'
|
752 |
);
|
753 |
|
754 |
}
|
755 |
}
|
756 |
}
|
757 |
|
|
|
758 |
if (!$result['rules-needed-update']) {
|
759 |
Messenger::addMessage(
|
760 |
'success',
|
762 |
);
|
763 |
} else {
|
764 |
$rulesResult = $result['htaccess-result'];
|
765 |
+
list($success, $successfullWrites, $successfulDeactivations, $failedWrites, $failedDeactivations) = $rulesResult;
|
766 |
+
|
767 |
+
$msg = '<p>Configuration was saved</p>';
|
768 |
+
|
769 |
+
if (count($successfullWrites) > 0) {
|
770 |
+
$msg .= '<p>Rewrite rules were saved to the following files:</p>';
|
771 |
+
foreach ($successfullWrites as $rootId) {
|
772 |
+
$msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
|
773 |
+
}
|
774 |
+
}
|
775 |
+
|
776 |
+
if (count($successfulDeactivations) > 0) {
|
777 |
+
$msg .= '<p>Rewrite rules were removed from the following files:</p>';
|
778 |
+
foreach ($successfulDeactivations as $rootId) {
|
779 |
+
$msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
|
780 |
+
}
|
781 |
+
}
|
782 |
+
|
783 |
+
Messenger::addMessage(
|
784 |
+
($success ? 'success' : 'info'),
|
785 |
+
$msg
|
786 |
+
);
|
787 |
+
|
788 |
+
|
789 |
+
if (count($failedWrites) > 0) {
|
790 |
+
$msg = '<p>Failed writing rewrite rules to the following files:</p>';
|
791 |
+
foreach ($failedWrites as $rootId) {
|
792 |
+
$msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
|
793 |
+
}
|
794 |
+
$msg .= 'You need to change the file permissions to allow WebP Express to save the rules.';
|
795 |
+
Messenger::addMessage('error', $msg);
|
796 |
+
} else {
|
797 |
+
if (count($failedDeactivations) > 0) {
|
798 |
+
$msg = '<p>Failed deleting unused rewrite rules in the following files:</p>';
|
799 |
+
foreach ($failedDeactivations as $rootId) {
|
800 |
+
$msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
|
801 |
+
}
|
802 |
+
$msg .= 'You need to change the file permissions to allow WebP Express to remove the rules or ' .
|
803 |
+
'remove them manually';
|
804 |
+
Messenger::addMessage('error', $msg);
|
805 |
+
}
|
806 |
+
}
|
807 |
+
|
808 |
/*
|
809 |
'mainResult' // 'index', 'wp-content' or 'failed'
|
810 |
'minRequired' // 'index' or 'wp-content'
|
817 |
'uploadFailed' // true if failed to write to plugin folder (it only tries that, if pluginToo == 'yes')
|
818 |
'uploadFailedBadly' // true if plugin failed AND it seems we have rewrite rules there
|
819 |
*/
|
820 |
+
/*
|
821 |
$mainResult = $rulesResult['mainResult'];
|
822 |
$rules = $rulesResult['rules'];
|
823 |
|
906 |
'Please grant write permmissions to you uploads folder. Otherwise uploaded mages will not be converted to webp'
|
907 |
);
|
908 |
}
|
909 |
+
}*/
|
910 |
}
|
911 |
}
|
912 |
|
vendor/composer/autoload_classmap.php
CHANGED
@@ -6,87 +6,4 @@ $vendorDir = dirname(dirname(__FILE__));
|
|
6 |
$baseDir = dirname($vendorDir);
|
7 |
|
8 |
return array(
|
9 |
-
'DOMUtilForWebP\\ImageUrlReplacer' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
|
10 |
-
'DOMUtilForWebP\\PictureTags' => $vendorDir . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
|
11 |
-
'ImageMimeTypeGuesser\\Detectors\\AbstractDetector' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php',
|
12 |
-
'ImageMimeTypeGuesser\\Detectors\\ExifImageType' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php',
|
13 |
-
'ImageMimeTypeGuesser\\Detectors\\FInfo' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php',
|
14 |
-
'ImageMimeTypeGuesser\\Detectors\\GetImageSize' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/GetImageSize.php',
|
15 |
-
'ImageMimeTypeGuesser\\Detectors\\MimeContentType' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/MimeContentType.php',
|
16 |
-
'ImageMimeTypeGuesser\\Detectors\\SniffFirstFourBytes' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/SniffFirstFourBytes.php',
|
17 |
-
'ImageMimeTypeGuesser\\Detectors\\Stack' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/Detectors/Stack.php',
|
18 |
-
'ImageMimeTypeGuesser\\GuessFromExtension' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/GuessFromExtension.php',
|
19 |
-
'ImageMimeTypeGuesser\\ImageMimeTypeGuesser' => $vendorDir . '/rosell-dk/image-mime-type-guesser/src/ImageMimeTypeGuesser.php',
|
20 |
-
'WebPConvertCloudService\\AccessCheck' => $vendorDir . '/rosell-dk/webp-convert-cloud-service/src/AccessCheck.php',
|
21 |
-
'WebPConvertCloudService\\Serve' => $vendorDir . '/rosell-dk/webp-convert-cloud-service/src/Serve.php',
|
22 |
-
'WebPConvertCloudService\\WebPConvertCloudService' => $vendorDir . '/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php',
|
23 |
-
'WebPConvert\\Convert\\ConverterFactory' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/ConverterFactory.php',
|
24 |
-
'WebPConvert\\Convert\\Converters\\AbstractConverter' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/AbstractConverter.php',
|
25 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\AutoQualityTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/AutoQualityTrait.php',
|
26 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\DestinationPreparationTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/DestinationPreparationTrait.php',
|
27 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\LoggerTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/LoggerTrait.php',
|
28 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\OptionsTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/OptionsTrait.php',
|
29 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\WarningLoggerTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/WarningLoggerTrait.php',
|
30 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\CloudConverterTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/CloudConverterTrait.php',
|
31 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\CurlTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/CurlTrait.php',
|
32 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\EncodingAutoTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/EncodingAutoTrait.php',
|
33 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\ExecTrait' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/ExecTrait.php',
|
34 |
-
'WebPConvert\\Convert\\Converters\\Cwebp' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php',
|
35 |
-
'WebPConvert\\Convert\\Converters\\Ewww' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Ewww.php',
|
36 |
-
'WebPConvert\\Convert\\Converters\\Gd' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Gd.php',
|
37 |
-
'WebPConvert\\Convert\\Converters\\Gmagick' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Gmagick.php',
|
38 |
-
'WebPConvert\\Convert\\Converters\\GmagickBinary' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/GmagickBinary.php',
|
39 |
-
'WebPConvert\\Convert\\Converters\\GraphicsMagick' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/GraphicsMagick.php',
|
40 |
-
'WebPConvert\\Convert\\Converters\\ImageMagick' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/ImageMagick.php',
|
41 |
-
'WebPConvert\\Convert\\Converters\\Imagick' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Imagick.php',
|
42 |
-
'WebPConvert\\Convert\\Converters\\ImagickBinary' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/ImagickBinary.php',
|
43 |
-
'WebPConvert\\Convert\\Converters\\Stack' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Stack.php',
|
44 |
-
'WebPConvert\\Convert\\Converters\\Vips' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Vips.php',
|
45 |
-
'WebPConvert\\Convert\\Converters\\Wpc' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Converters/Wpc.php',
|
46 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailedException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailedException.php',
|
47 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConversionSkippedException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConversionSkippedException.php',
|
48 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConverterNotOperationalException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConverterNotOperationalException.php',
|
49 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConverterNotOperational\\InvalidApiKeyException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConverterNotOperational/InvalidApiKeyException.php',
|
50 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConverterNotOperational\\SystemRequirementsNotMetException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConverterNotOperational/SystemRequirementsNotMetException.php',
|
51 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\FileSystemProblemsException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/FileSystemProblemsException.php',
|
52 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\FileSystemProblems\\CreateDestinationFileException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/FileSystemProblems/CreateDestinationFileException.php',
|
53 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\FileSystemProblems\\CreateDestinationFolderException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/FileSystemProblems/CreateDestinationFolderException.php',
|
54 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInputException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInputException.php',
|
55 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInput\\ConverterNotFoundException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInput/ConverterNotFoundException.php',
|
56 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInput\\InvalidImageTypeException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInput/InvalidImageTypeException.php',
|
57 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInput\\TargetNotFoundException' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInput/TargetNotFoundException.php',
|
58 |
-
'WebPConvert\\Convert\\Helpers\\JpegQualityDetector' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Helpers/JpegQualityDetector.php',
|
59 |
-
'WebPConvert\\Convert\\Helpers\\PhpIniSizes' => $vendorDir . '/rosell-dk/webp-convert/src/Convert/Helpers/PhpIniSizes.php',
|
60 |
-
'WebPConvert\\Exceptions\\InvalidInputException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/InvalidInputException.php',
|
61 |
-
'WebPConvert\\Exceptions\\InvalidInput\\InvalidImageTypeException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/InvalidInput/InvalidImageTypeException.php',
|
62 |
-
'WebPConvert\\Exceptions\\InvalidInput\\TargetNotFoundException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/InvalidInput/TargetNotFoundException.php',
|
63 |
-
'WebPConvert\\Exceptions\\WebPConvertException' => $vendorDir . '/rosell-dk/webp-convert/src/Exceptions/WebPConvertException.php',
|
64 |
-
'WebPConvert\\Helpers\\InputValidator' => $vendorDir . '/rosell-dk/webp-convert/src/Helpers/InputValidator.php',
|
65 |
-
'WebPConvert\\Helpers\\MimeType' => $vendorDir . '/rosell-dk/webp-convert/src/Helpers/MimeType.php',
|
66 |
-
'WebPConvert\\Helpers\\PathChecker' => $vendorDir . '/rosell-dk/webp-convert/src/Helpers/PathChecker.php',
|
67 |
-
'WebPConvert\\Loggers\\BaseLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/BaseLogger.php',
|
68 |
-
'WebPConvert\\Loggers\\BufferLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/BufferLogger.php',
|
69 |
-
'WebPConvert\\Loggers\\EchoLogger' => $vendorDir . '/rosell-dk/webp-convert/src/Loggers/EchoLogger.php',
|
70 |
-
'WebPConvert\\Options\\ArrayOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/ArrayOption.php',
|
71 |
-
'WebPConvert\\Options\\BooleanOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/BooleanOption.php',
|
72 |
-
'WebPConvert\\Options\\Exceptions\\InvalidOptionTypeException' => $vendorDir . '/rosell-dk/webp-convert/src/Options/Exceptions/InvalidOptionTypeException.php',
|
73 |
-
'WebPConvert\\Options\\Exceptions\\InvalidOptionValueException' => $vendorDir . '/rosell-dk/webp-convert/src/Options/Exceptions/InvalidOptionValueException.php',
|
74 |
-
'WebPConvert\\Options\\Exceptions\\OptionNotFoundException' => $vendorDir . '/rosell-dk/webp-convert/src/Options/Exceptions/OptionNotFoundException.php',
|
75 |
-
'WebPConvert\\Options\\GhostOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/GhostOption.php',
|
76 |
-
'WebPConvert\\Options\\IntegerOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/IntegerOption.php',
|
77 |
-
'WebPConvert\\Options\\IntegerOrNullOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/IntegerOrNullOption.php',
|
78 |
-
'WebPConvert\\Options\\MetadataOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/MetadataOption.php',
|
79 |
-
'WebPConvert\\Options\\Option' => $vendorDir . '/rosell-dk/webp-convert/src/Options/Option.php',
|
80 |
-
'WebPConvert\\Options\\Options' => $vendorDir . '/rosell-dk/webp-convert/src/Options/Options.php',
|
81 |
-
'WebPConvert\\Options\\QualityOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/QualityOption.php',
|
82 |
-
'WebPConvert\\Options\\SensitiveArrayOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/SensitiveArrayOption.php',
|
83 |
-
'WebPConvert\\Options\\SensitiveStringOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/SensitiveStringOption.php',
|
84 |
-
'WebPConvert\\Options\\StringOption' => $vendorDir . '/rosell-dk/webp-convert/src/Options/StringOption.php',
|
85 |
-
'WebPConvert\\Serve\\Exceptions\\ServeFailedException' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/Exceptions/ServeFailedException.php',
|
86 |
-
'WebPConvert\\Serve\\Header' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/Header.php',
|
87 |
-
'WebPConvert\\Serve\\Report' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/Report.php',
|
88 |
-
'WebPConvert\\Serve\\ServeConvertedWebP' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/ServeConvertedWebP.php',
|
89 |
-
'WebPConvert\\Serve\\ServeConvertedWebPWithErrorHandling' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/ServeConvertedWebPWithErrorHandling.php',
|
90 |
-
'WebPConvert\\Serve\\ServeFile' => $vendorDir . '/rosell-dk/webp-convert/src/Serve/ServeFile.php',
|
91 |
-
'WebPConvert\\WebPConvert' => $vendorDir . '/rosell-dk/webp-convert/src/WebPConvert.php',
|
92 |
);
|
6 |
$baseDir = dirname($vendorDir);
|
7 |
|
8 |
return array(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
);
|
vendor/composer/autoload_static.php
CHANGED
@@ -41,98 +41,11 @@ class ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243
|
|
41 |
),
|
42 |
);
|
43 |
|
44 |
-
public static $classMap = array (
|
45 |
-
'DOMUtilForWebP\\ImageUrlReplacer' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/ImageUrlReplacer.php',
|
46 |
-
'DOMUtilForWebP\\PictureTags' => __DIR__ . '/..' . '/rosell-dk/dom-util-for-webp/src/PictureTags.php',
|
47 |
-
'ImageMimeTypeGuesser\\Detectors\\AbstractDetector' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/AbstractDetector.php',
|
48 |
-
'ImageMimeTypeGuesser\\Detectors\\ExifImageType' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/ExifImageType.php',
|
49 |
-
'ImageMimeTypeGuesser\\Detectors\\FInfo' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/FInfo.php',
|
50 |
-
'ImageMimeTypeGuesser\\Detectors\\GetImageSize' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/GetImageSize.php',
|
51 |
-
'ImageMimeTypeGuesser\\Detectors\\MimeContentType' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/MimeContentType.php',
|
52 |
-
'ImageMimeTypeGuesser\\Detectors\\SniffFirstFourBytes' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/SniffFirstFourBytes.php',
|
53 |
-
'ImageMimeTypeGuesser\\Detectors\\Stack' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/Detectors/Stack.php',
|
54 |
-
'ImageMimeTypeGuesser\\GuessFromExtension' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/GuessFromExtension.php',
|
55 |
-
'ImageMimeTypeGuesser\\ImageMimeTypeGuesser' => __DIR__ . '/..' . '/rosell-dk/image-mime-type-guesser/src/ImageMimeTypeGuesser.php',
|
56 |
-
'WebPConvertCloudService\\AccessCheck' => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src/AccessCheck.php',
|
57 |
-
'WebPConvertCloudService\\Serve' => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src/Serve.php',
|
58 |
-
'WebPConvertCloudService\\WebPConvertCloudService' => __DIR__ . '/..' . '/rosell-dk/webp-convert-cloud-service/src/WebPConvertCloudService.php',
|
59 |
-
'WebPConvert\\Convert\\ConverterFactory' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/ConverterFactory.php',
|
60 |
-
'WebPConvert\\Convert\\Converters\\AbstractConverter' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/AbstractConverter.php',
|
61 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\AutoQualityTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/AutoQualityTrait.php',
|
62 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\DestinationPreparationTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/DestinationPreparationTrait.php',
|
63 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\LoggerTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/LoggerTrait.php',
|
64 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\OptionsTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/OptionsTrait.php',
|
65 |
-
'WebPConvert\\Convert\\Converters\\BaseTraits\\WarningLoggerTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/BaseTraits/WarningLoggerTrait.php',
|
66 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\CloudConverterTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/CloudConverterTrait.php',
|
67 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\CurlTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/CurlTrait.php',
|
68 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\EncodingAutoTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/EncodingAutoTrait.php',
|
69 |
-
'WebPConvert\\Convert\\Converters\\ConverterTraits\\ExecTrait' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/ConverterTraits/ExecTrait.php',
|
70 |
-
'WebPConvert\\Convert\\Converters\\Cwebp' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php',
|
71 |
-
'WebPConvert\\Convert\\Converters\\Ewww' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Ewww.php',
|
72 |
-
'WebPConvert\\Convert\\Converters\\Gd' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Gd.php',
|
73 |
-
'WebPConvert\\Convert\\Converters\\Gmagick' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Gmagick.php',
|
74 |
-
'WebPConvert\\Convert\\Converters\\GmagickBinary' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/GmagickBinary.php',
|
75 |
-
'WebPConvert\\Convert\\Converters\\GraphicsMagick' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/GraphicsMagick.php',
|
76 |
-
'WebPConvert\\Convert\\Converters\\ImageMagick' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/ImageMagick.php',
|
77 |
-
'WebPConvert\\Convert\\Converters\\Imagick' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Imagick.php',
|
78 |
-
'WebPConvert\\Convert\\Converters\\ImagickBinary' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/ImagickBinary.php',
|
79 |
-
'WebPConvert\\Convert\\Converters\\Stack' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Stack.php',
|
80 |
-
'WebPConvert\\Convert\\Converters\\Vips' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Vips.php',
|
81 |
-
'WebPConvert\\Convert\\Converters\\Wpc' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Converters/Wpc.php',
|
82 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailedException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailedException.php',
|
83 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConversionSkippedException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConversionSkippedException.php',
|
84 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConverterNotOperationalException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConverterNotOperationalException.php',
|
85 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConverterNotOperational\\InvalidApiKeyException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConverterNotOperational/InvalidApiKeyException.php',
|
86 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\ConverterNotOperational\\SystemRequirementsNotMetException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/ConverterNotOperational/SystemRequirementsNotMetException.php',
|
87 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\FileSystemProblemsException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/FileSystemProblemsException.php',
|
88 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\FileSystemProblems\\CreateDestinationFileException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/FileSystemProblems/CreateDestinationFileException.php',
|
89 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\FileSystemProblems\\CreateDestinationFolderException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/FileSystemProblems/CreateDestinationFolderException.php',
|
90 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInputException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInputException.php',
|
91 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInput\\ConverterNotFoundException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInput/ConverterNotFoundException.php',
|
92 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInput\\InvalidImageTypeException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInput/InvalidImageTypeException.php',
|
93 |
-
'WebPConvert\\Convert\\Exceptions\\ConversionFailed\\InvalidInput\\TargetNotFoundException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Exceptions/ConversionFailed/InvalidInput/TargetNotFoundException.php',
|
94 |
-
'WebPConvert\\Convert\\Helpers\\JpegQualityDetector' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Helpers/JpegQualityDetector.php',
|
95 |
-
'WebPConvert\\Convert\\Helpers\\PhpIniSizes' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Convert/Helpers/PhpIniSizes.php',
|
96 |
-
'WebPConvert\\Exceptions\\InvalidInputException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/InvalidInputException.php',
|
97 |
-
'WebPConvert\\Exceptions\\InvalidInput\\InvalidImageTypeException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/InvalidInput/InvalidImageTypeException.php',
|
98 |
-
'WebPConvert\\Exceptions\\InvalidInput\\TargetNotFoundException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/InvalidInput/TargetNotFoundException.php',
|
99 |
-
'WebPConvert\\Exceptions\\WebPConvertException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Exceptions/WebPConvertException.php',
|
100 |
-
'WebPConvert\\Helpers\\InputValidator' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Helpers/InputValidator.php',
|
101 |
-
'WebPConvert\\Helpers\\MimeType' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Helpers/MimeType.php',
|
102 |
-
'WebPConvert\\Helpers\\PathChecker' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Helpers/PathChecker.php',
|
103 |
-
'WebPConvert\\Loggers\\BaseLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/BaseLogger.php',
|
104 |
-
'WebPConvert\\Loggers\\BufferLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/BufferLogger.php',
|
105 |
-
'WebPConvert\\Loggers\\EchoLogger' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Loggers/EchoLogger.php',
|
106 |
-
'WebPConvert\\Options\\ArrayOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/ArrayOption.php',
|
107 |
-
'WebPConvert\\Options\\BooleanOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/BooleanOption.php',
|
108 |
-
'WebPConvert\\Options\\Exceptions\\InvalidOptionTypeException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/Exceptions/InvalidOptionTypeException.php',
|
109 |
-
'WebPConvert\\Options\\Exceptions\\InvalidOptionValueException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/Exceptions/InvalidOptionValueException.php',
|
110 |
-
'WebPConvert\\Options\\Exceptions\\OptionNotFoundException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/Exceptions/OptionNotFoundException.php',
|
111 |
-
'WebPConvert\\Options\\GhostOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/GhostOption.php',
|
112 |
-
'WebPConvert\\Options\\IntegerOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/IntegerOption.php',
|
113 |
-
'WebPConvert\\Options\\IntegerOrNullOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/IntegerOrNullOption.php',
|
114 |
-
'WebPConvert\\Options\\MetadataOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/MetadataOption.php',
|
115 |
-
'WebPConvert\\Options\\Option' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/Option.php',
|
116 |
-
'WebPConvert\\Options\\Options' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/Options.php',
|
117 |
-
'WebPConvert\\Options\\QualityOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/QualityOption.php',
|
118 |
-
'WebPConvert\\Options\\SensitiveArrayOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/SensitiveArrayOption.php',
|
119 |
-
'WebPConvert\\Options\\SensitiveStringOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/SensitiveStringOption.php',
|
120 |
-
'WebPConvert\\Options\\StringOption' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Options/StringOption.php',
|
121 |
-
'WebPConvert\\Serve\\Exceptions\\ServeFailedException' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/Exceptions/ServeFailedException.php',
|
122 |
-
'WebPConvert\\Serve\\Header' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/Header.php',
|
123 |
-
'WebPConvert\\Serve\\Report' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/Report.php',
|
124 |
-
'WebPConvert\\Serve\\ServeConvertedWebP' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/ServeConvertedWebP.php',
|
125 |
-
'WebPConvert\\Serve\\ServeConvertedWebPWithErrorHandling' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/ServeConvertedWebPWithErrorHandling.php',
|
126 |
-
'WebPConvert\\Serve\\ServeFile' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/Serve/ServeFile.php',
|
127 |
-
'WebPConvert\\WebPConvert' => __DIR__ . '/..' . '/rosell-dk/webp-convert/src/WebPConvert.php',
|
128 |
-
);
|
129 |
-
|
130 |
public static function getInitializer(ClassLoader $loader)
|
131 |
{
|
132 |
return \Closure::bind(function () use ($loader) {
|
133 |
$loader->prefixLengthsPsr4 = ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::$prefixLengthsPsr4;
|
134 |
$loader->prefixDirsPsr4 = ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::$prefixDirsPsr4;
|
135 |
-
$loader->classMap = ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::$classMap;
|
136 |
|
137 |
}, null, ClassLoader::class);
|
138 |
}
|
41 |
),
|
42 |
);
|
43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
public static function getInitializer(ClassLoader $loader)
|
45 |
{
|
46 |
return \Closure::bind(function () use ($loader) {
|
47 |
$loader->prefixLengthsPsr4 = ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::$prefixLengthsPsr4;
|
48 |
$loader->prefixDirsPsr4 = ComposerStaticInit16597e36dd1bfcd787ed5a8e6d908243::$prefixDirsPsr4;
|
|
|
49 |
|
50 |
}, null, ClassLoader::class);
|
51 |
}
|
vendor/composer/installed.json
CHANGED
@@ -115,17 +115,17 @@
|
|
115 |
},
|
116 |
{
|
117 |
"name": "rosell-dk/webp-convert",
|
118 |
-
"version": "2.1.
|
119 |
-
"version_normalized": "2.1.
|
120 |
"source": {
|
121 |
"type": "git",
|
122 |
"url": "https://github.com/rosell-dk/webp-convert.git",
|
123 |
-
"reference": "
|
124 |
},
|
125 |
"dist": {
|
126 |
"type": "zip",
|
127 |
-
"url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/
|
128 |
-
"reference": "
|
129 |
"shasum": ""
|
130 |
},
|
131 |
"require": {
|
@@ -143,7 +143,7 @@
|
|
143 |
"ext-vips": "to use Vips extension for converting.",
|
144 |
"php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
|
145 |
},
|
146 |
-
"time": "2019-08-
|
147 |
"type": "library",
|
148 |
"extra": {
|
149 |
"scripts-descriptions": {
|
115 |
},
|
116 |
{
|
117 |
"name": "rosell-dk/webp-convert",
|
118 |
+
"version": "2.1.6",
|
119 |
+
"version_normalized": "2.1.6.0",
|
120 |
"source": {
|
121 |
"type": "git",
|
122 |
"url": "https://github.com/rosell-dk/webp-convert.git",
|
123 |
+
"reference": "5cb744d2786468fb51883d1f3a90562166df3320"
|
124 |
},
|
125 |
"dist": {
|
126 |
"type": "zip",
|
127 |
+
"url": "https://api.github.com/repos/rosell-dk/webp-convert/zipball/5cb744d2786468fb51883d1f3a90562166df3320",
|
128 |
+
"reference": "5cb744d2786468fb51883d1f3a90562166df3320",
|
129 |
"shasum": ""
|
130 |
},
|
131 |
"require": {
|
143 |
"ext-vips": "to use Vips extension for converting.",
|
144 |
"php-stan/php-stan": "Suggested for dev, in order to analyse code before committing"
|
145 |
},
|
146 |
+
"time": "2019-08-10T22:04:08+00:00",
|
147 |
"type": "library",
|
148 |
"extra": {
|
149 |
"scripts-descriptions": {
|
vendor/rosell-dk/webp-convert/.php_cs.dist
DELETED
@@ -1,19 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
$finder = PhpCsFixer\Finder::create()
|
4 |
-
->exclude('tests')
|
5 |
-
->in(__DIR__)
|
6 |
-
;
|
7 |
-
|
8 |
-
$config = PhpCsFixer\Config::create();
|
9 |
-
$config
|
10 |
-
->setRules([
|
11 |
-
'@PSR2' => true,
|
12 |
-
'array_syntax' => [
|
13 |
-
'syntax' => 'short',
|
14 |
-
],
|
15 |
-
])
|
16 |
-
->setFinder($finder)
|
17 |
-
;
|
18 |
-
|
19 |
-
return $config;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/rosell-dk/webp-convert/BACKERS.md
CHANGED
@@ -13,6 +13,7 @@ To become a backer yourself, visit [my page at patreon](https://www.patreon.com/
|
|
13 |
| Name | Date | Message (max 70 chars, plain text only) |
|
14 |
| --------------------- | ---------- | ----------------------------------------------------------------------- |
|
15 |
| Tammy Valgardson | 2018-12-27 | |
|
|
|
16 |
|
17 |
<sub>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</sub>
|
18 |
|
13 |
| Name | Date | Message (max 70 chars, plain text only) |
|
14 |
| --------------------- | ---------- | ----------------------------------------------------------------------- |
|
15 |
| Tammy Valgardson | 2018-12-27 | |
|
16 |
+
| Max Kreminsky | 2019-08-02 | |
|
17 |
|
18 |
<sub>I reserve the right to disallow inappropriate messages. No Trump hooting or bashing here, please. And don't be aggressive, obscene or anything unpleasant. But I don't have to point that out, do I?</sub>
|
19 |
|
vendor/rosell-dk/webp-convert/README.md
CHANGED
@@ -94,17 +94,22 @@ The old introduction (for 1.3.9) is available here: [docs/v1.3/serving/convert-a
|
|
94 |
The library can be used to create a *WebP On Demand* solution, which automatically serves WebP images instead of jpeg/pngs for browsers that supports WebP. To set this up, follow what's described [in this tutorial (not updated for 2.0 yet)](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/webp-on-demand.md).
|
95 |
|
96 |
|
97 |
-
## WebP Convert
|
98 |
-
*WebP Convert* is used in the following projects:
|
99 |
|
100 |
-
|
101 |
-
|
102 |
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
A cloud service based on WebPConvert
|
105 |
|
106 |
-
|
107 |
-
|
108 |
|
109 |
## Supporting WebP Convert
|
110 |
Bread on the table don't come for free, even though this library does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to have both:
|
94 |
The library can be used to create a *WebP On Demand* solution, which automatically serves WebP images instead of jpeg/pngs for browsers that supports WebP. To set this up, follow what's described [in this tutorial (not updated for 2.0 yet)](https://github.com/rosell-dk/webp-convert/blob/master/docs/v1.3/webp-on-demand/webp-on-demand.md).
|
95 |
|
96 |
|
97 |
+
## Projects using WebP Convert
|
|
|
98 |
|
99 |
+
### CMS plugins using WebP Convert
|
100 |
+
This library is used as the engine to provide webp conversions to a handful of platforms. Hopefully this list will be growing over time. Currently there are plugins / extensions / modules / whatever the term is for the following CMS'es (ordered by [market share](https://w3techs.com/technologies/overview/content_management/all)):
|
101 |
|
102 |
+
- [Wordpress](https://github.com/rosell-dk/webp-express)
|
103 |
+
- [Contao](https://github.com/postyou/contao-webp-bundle)
|
104 |
+
- [Kirby](https://github.com/S1SYPHOS/kirby-webp)
|
105 |
+
|
106 |
+
### Other projects using WebP Convert
|
107 |
+
|
108 |
+
- [webp-convert-cloud-service](https://github.com/rosell-dk/webp-convert-cloud-service)
|
109 |
A cloud service based on WebPConvert
|
110 |
|
111 |
+
- [webp-convert-concat](https://github.com/rosell-dk/webp-convert-concat)
|
112 |
+
The webp-convert library and its dependents as a single PHP file (or two)
|
113 |
|
114 |
## Supporting WebP Convert
|
115 |
Bread on the table don't come for free, even though this library does, and always will. I enjoy developing this, and supporting you guys, but I kind of need the bread too. Please make it possible for me to have both:
|
vendor/rosell-dk/webp-convert/composer.json
CHANGED
@@ -12,24 +12,7 @@
|
|
12 |
"@composer validate --no-check-all --strict",
|
13 |
"@phpstan-global"
|
14 |
],
|
15 |
-
"test":
|
16 |
-
"@test-wod-build",
|
17 |
-
"@test-webp-convert-build",
|
18 |
-
"@test-src"
|
19 |
-
],
|
20 |
-
"test-src-build": [
|
21 |
-
"@test-wod-build",
|
22 |
-
"@test-webp-convert-build"
|
23 |
-
],
|
24 |
-
"test-wod-build": [
|
25 |
-
"phpunit --no-coverage build-tests-wod",
|
26 |
-
"phpunit --no-coverage --bootstrap tests/bootstrap-wod-test.php tests"
|
27 |
-
],
|
28 |
-
"test-webp-convert-build": [
|
29 |
-
"phpunit --no-coverage build-tests-webp-convert",
|
30 |
-
"phpunit --no-coverage --bootstrap tests/bootstrap-webp-convert-test.php tests"
|
31 |
-
],
|
32 |
-
"test-src": "phpunit --coverage-text",
|
33 |
"phpunit": "phpunit --coverage-text",
|
34 |
"test-no-cov": "phpunit --no-coverage",
|
35 |
"cs-fix-all": [
|
12 |
"@composer validate --no-check-all --strict",
|
13 |
"@phpstan-global"
|
14 |
],
|
15 |
+
"test": "phpunit --coverage-text",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
"phpunit": "phpunit --coverage-text",
|
17 |
"test-no-cov": "phpunit --no-coverage",
|
18 |
"cs-fix-all": [
|
vendor/rosell-dk/webp-convert/phpdox.xml
DELETED
@@ -1,9 +0,0 @@
|
|
1 |
-
<?xml version="1.0" encoding="utf-8" ?>
|
2 |
-
<phpdox xmlns="http://xml.phpdox.net/config">
|
3 |
-
<project name="WebP Convert" source="${basedir}/src" workdir="${basedir}/build/api/xml">
|
4 |
-
<collector backend="parser" />
|
5 |
-
<generator output="${basedir}/build/api">
|
6 |
-
<build engine="html" output="html"/>
|
7 |
-
</generator>
|
8 |
-
</project>
|
9 |
-
</phpdox>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/rosell-dk/webp-convert/phpstan.neon
DELETED
@@ -1,28 +0,0 @@
|
|
1 |
-
parameters:
|
2 |
-
reportUnmatchedIgnoredErrors: false
|
3 |
-
ignoreErrors:
|
4 |
-
- '#Instantiated class Imagick not found.#'
|
5 |
-
- '#Instantiated class Gmagick not found.#'
|
6 |
-
- '#Caught class ImagickException not found.#'
|
7 |
-
- '# on an unknown class Imagick.#'
|
8 |
-
- '# on an unknown class Gmagick.#'
|
9 |
-
-
|
10 |
-
message: '#Strict comparison using === between int and false will always evaluate to false.#'
|
11 |
-
path: %currentWorkingDirectory%/src/Convert/Converters/Gd.php
|
12 |
-
-
|
13 |
-
message: '#Strict comparison using === between resource and false will always evaluate to false.#'
|
14 |
-
path: %currentWorkingDirectory%/src/Convert/Converters/Ewww.php
|
15 |
-
-
|
16 |
-
message: '#Strict comparison using === between resource and false will always evaluate to false.#'
|
17 |
-
path: %currentWorkingDirectory%/src/Convert/Converters/ConverterTraits/CurlTrait.php
|
18 |
-
-
|
19 |
-
message: '#Strict comparison using === between resource and false will always evaluate to false.#'
|
20 |
-
path: %currentWorkingDirectory%/src/Convert/BaseConverters/AbstractCloudCurlConverter.php
|
21 |
-
- '#Function vips_call invoked with 4 parameters, 2 required.#'
|
22 |
-
- '#Function vips_.* not found.#'
|
23 |
-
-
|
24 |
-
message: '#SystemRequirementsNotMet#'
|
25 |
-
path: %currentWorkingDirectory%/src/Convert/Converters/ConverterTraits/ExecTrait.php
|
26 |
-
-
|
27 |
-
message: '#Call to function is_callable\(\) with array.* always evaluate to false#'
|
28 |
-
path: %currentWorkingDirectory%/src/Convert/ConverterFactory.php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/rosell-dk/webp-convert/phpunit.xml.dist
DELETED
@@ -1,41 +0,0 @@
|
|
1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
-
|
3 |
-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
4 |
-
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
5 |
-
backupGlobals="false"
|
6 |
-
backupStaticAttributes="false"
|
7 |
-
colors="true"
|
8 |
-
convertErrorsToExceptions="true"
|
9 |
-
convertNoticesToExceptions="true"
|
10 |
-
convertWarningsToExceptions="false"
|
11 |
-
processIsolation="false"
|
12 |
-
stopOnFailure="false"
|
13 |
-
bootstrap="vendor/autoload.php"
|
14 |
-
|
15 |
-
>
|
16 |
-
<testsuites>
|
17 |
-
<testsuite name="WebPConvert Test Suite">
|
18 |
-
<directory>./tests/</directory>
|
19 |
-
</testsuite>
|
20 |
-
</testsuites>
|
21 |
-
|
22 |
-
<filter>
|
23 |
-
<whitelist>
|
24 |
-
<directory suffix=".php">src/</directory>
|
25 |
-
<exclude>
|
26 |
-
<directory>./vendor</directory>
|
27 |
-
<directory>./tests</directory>
|
28 |
-
<directory>./build-tests-wod</directory>
|
29 |
-
<directory>./build-tests-complete</directory>
|
30 |
-
</exclude>
|
31 |
-
</whitelist>
|
32 |
-
</filter>
|
33 |
-
|
34 |
-
<logging>
|
35 |
-
<log type="junit" target="build/report.junit.xml"/>
|
36 |
-
<log type="coverage-clover" target="coverage.clover"/>
|
37 |
-
<log type="coverage-text" target="build/coverage.txt"/>
|
38 |
-
<log type="coverage-html" target="build/coverage"/>
|
39 |
-
</logging>
|
40 |
-
|
41 |
-
</phpunit>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vendor/rosell-dk/webp-convert/src/Convert/Converters/Cwebp.php
CHANGED
@@ -269,10 +269,16 @@ class Cwebp extends AbstractConverter
|
|
269 |
|
270 |
// command-line-options
|
271 |
if ($options['command-line-options']) {
|
|
|
|
|
272 |
array_push(
|
273 |
$cmdOptions,
|
274 |
...self::escapeShellArgOnCommandLineOptions($options['command-line-options'])
|
275 |
);
|
|
|
|
|
|
|
|
|
276 |
}
|
277 |
|
278 |
// Source file
|
269 |
|
270 |
// command-line-options
|
271 |
if ($options['command-line-options']) {
|
272 |
+
/*
|
273 |
+
In some years, we can use the splat instead (requires PHP 5.6)
|
274 |
array_push(
|
275 |
$cmdOptions,
|
276 |
...self::escapeShellArgOnCommandLineOptions($options['command-line-options'])
|
277 |
);
|
278 |
+
*/
|
279 |
+
foreach (self::escapeShellArgOnCommandLineOptions($options['command-line-options']) as $cmdLineOption) {
|
280 |
+
array_push($cmdOptions, $cmdLineOption);
|
281 |
+
}
|
282 |
}
|
283 |
|
284 |
// Source file
|
vendor/rosell-dk/webp-convert/src/Helpers/Sanitize.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WebPConvert\Helpers;
|
4 |
+
|
5 |
+
class Sanitize
|
6 |
+
{
|
7 |
+
|
8 |
+
/**
|
9 |
+
* The NUL character is a demon, because it can be used to bypass other tests
|
10 |
+
* See https://st-g.de/2011/04/doing-filename-checks-securely-in-PHP.
|
11 |
+
*
|
12 |
+
* @param string $string string remove NUL characters in
|
13 |
+
*/
|
14 |
+
public static function removeNUL($string)
|
15 |
+
{
|
16 |
+
return str_replace(chr(0), '', $string);
|
17 |
+
}
|
18 |
+
|
19 |
+
public static function removeStreamWrappers($string)
|
20 |
+
{
|
21 |
+
return preg_replace('#^\\w+://#', '', $string);
|
22 |
+
}
|
23 |
+
|
24 |
+
public static function path($string)
|
25 |
+
{
|
26 |
+
$string = self::removeNUL($string);
|
27 |
+
$string = self::removeStreamWrappers($string);
|
28 |
+
return $string;
|
29 |
+
}
|
30 |
+
}
|
vendor/rosell-dk/webp-convert/src/Options/Options.php
CHANGED
@@ -34,15 +34,24 @@ class Options
|
|
34 |
*
|
35 |
* Conveniently add several options in one call.
|
36 |
*
|
37 |
-
* @param Option[] ...$options Array of options objects to add
|
38 |
* @return void
|
39 |
*/
|
40 |
-
public function addOptions(
|
41 |
{
|
|
|
42 |
foreach ($options as $option) {
|
43 |
$this->addOption($option);
|
44 |
}
|
45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
/**
|
48 |
* Set the value of an option.
|
34 |
*
|
35 |
* Conveniently add several options in one call.
|
36 |
*
|
|
|
37 |
* @return void
|
38 |
*/
|
39 |
+
public function addOptions()
|
40 |
{
|
41 |
+
$options = func_get_args();
|
42 |
foreach ($options as $option) {
|
43 |
$this->addOption($option);
|
44 |
}
|
45 |
}
|
46 |
+
/*
|
47 |
+
In some years, we can use the splat instead (requires PHP 5.6):
|
48 |
+
@param Option[] ...$options Array of options objects to add
|
49 |
+
public function addOptions(...$options)
|
50 |
+
{
|
51 |
+
foreach ($options as $option) {
|
52 |
+
$this->addOption($option);
|
53 |
+
}
|
54 |
+
}*/
|
55 |
|
56 |
/**
|
57 |
* Set the value of an option.
|
webp-express.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
* Plugin Name: WebP Express
|
4 |
* Plugin URI: https://github.com/rosell-dk/webp-express
|
5 |
* Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
|
6 |
-
* Version: 0.
|
7 |
* Author: Bjørn Rosell
|
8 |
* Author URI: https://www.bitwise-it.dk
|
9 |
* License: GPL2
|
3 |
* Plugin Name: WebP Express
|
4 |
* Plugin URI: https://github.com/rosell-dk/webp-express
|
5 |
* Description: Serve autogenerated WebP images instead of jpeg/png to browsers that supports WebP. Works on anything (media library images, galleries, theme images etc).
|
6 |
+
* Version: 0.15.0
|
7 |
* Author: Bjørn Rosell
|
8 |
* Author URI: https://www.bitwise-it.dk
|
9 |
* License: GPL2
|
wod/webp-on-demand.php
CHANGED
@@ -1,223 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
|
4 |
-
|
5 |
-
error_reporting(E_ALL);
|
6 |
-
ini_set('display_errors', 1);
|
7 |
-
|
8 |
-
use \WebPConvert\WebPConvert;
|
9 |
-
use \WebPConvert\Serve\ServeConvertedWebP;
|
10 |
-
use \WebPExpress\ConvertHelperIndependent;
|
11 |
-
use \WebPExpress\Sanitize;
|
12 |
-
use \WebPExpress\SanityCheck;
|
13 |
-
use \WebPExpress\SanityException;
|
14 |
-
use \WebPExpress\ValidateException;
|
15 |
-
use \WebPExpress\Validate;
|
16 |
-
|
17 |
-
class WebPOnDemand
|
18 |
-
{
|
19 |
-
|
20 |
-
private static $docRoot;
|
21 |
-
|
22 |
-
public static function exitWithError($msg) {
|
23 |
-
header('X-WebP-Express-Error: ' . $msg, true);
|
24 |
-
echo $msg;
|
25 |
-
exit;
|
26 |
-
}
|
27 |
-
|
28 |
-
/**
|
29 |
-
* Get environment variable set with mod_rewrite module
|
30 |
-
* Return false if the environment variable isn't found
|
31 |
-
*/
|
32 |
-
private static function getEnvPassedInRewriteRule($envName) {
|
33 |
-
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
|
34 |
-
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
35 |
-
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
36 |
-
// We simply look for an environment variable that ends with what we are looking for.
|
37 |
-
// (so make sure to make it unique)
|
38 |
-
$len = strlen($envName);
|
39 |
-
foreach ($_SERVER as $key => $item) {
|
40 |
-
if (substr($key, -$len) == $envName) {
|
41 |
-
return $item;
|
42 |
-
}
|
43 |
-
}
|
44 |
-
return false;
|
45 |
-
}
|
46 |
-
|
47 |
-
public static function process() {
|
48 |
-
|
49 |
-
include_once __DIR__ . "/../lib/classes/ConvertHelperIndependent.php";
|
50 |
-
include_once __DIR__ . '/../lib/classes/Sanitize.php';
|
51 |
-
include_once __DIR__ . '/../lib/classes/SanityCheck.php';
|
52 |
-
include_once __DIR__ . '/../lib/classes/SanityException.php';
|
53 |
-
include_once __DIR__ . '/../lib/classes/Validate.php';
|
54 |
-
include_once __DIR__ . '/../lib/classes/ValidateException.php';
|
55 |
-
|
56 |
-
// Check input
|
57 |
-
// --------------
|
58 |
-
try {
|
59 |
-
|
60 |
-
// Check DOCUMENT_ROOT
|
61 |
-
// ----------------------
|
62 |
-
$checking = 'DOCUMENT_ROOT';
|
63 |
-
$docRoot = SanityCheck::absPath($_SERVER["DOCUMENT_ROOT"]);
|
64 |
-
$docRoot = rtrim($docRoot, '/');
|
65 |
-
$docRoot = SanityCheck::absPathExistsAndIsDir($docRoot);
|
66 |
-
|
67 |
-
// Check wp-content
|
68 |
-
// ----------------------
|
69 |
-
|
70 |
-
// Passed in env variable?
|
71 |
-
$wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
|
72 |
-
if ($wpContentDirRel === false) {
|
73 |
-
|
74 |
-
// Passed in QS?
|
75 |
-
if (isset($_GET['wp-content'])) {
|
76 |
-
$wpContentDirRel = SanityCheck::pathWithoutDirectoryTraversal($_GET['wp-content']);
|
77 |
-
} else {
|
78 |
-
// In case above fails, fall back to standard location
|
79 |
-
$wpContentDirRel = 'wp-content';
|
80 |
-
}
|
81 |
-
}
|
82 |
-
|
83 |
-
// Check WebP Express content dir
|
84 |
-
// ---------------------------------
|
85 |
-
$checking = 'WebP Express content dir';
|
86 |
-
$webExpressContentDirAbs = SanityCheck::absPathExistsAndIsDir($docRoot . '/' . $wpContentDirRel . '/webp-express');
|
87 |
-
|
88 |
-
|
89 |
-
// Check config file name
|
90 |
-
// ---------------------------------
|
91 |
-
$checking = 'config file';
|
92 |
-
$configFilename = SanityCheck::absPathExistsAndIsFile($webExpressContentDirAbs . '/config/wod-options.json');
|
93 |
-
|
94 |
-
|
95 |
-
// Check config file
|
96 |
-
// --------------------
|
97 |
-
$configLoadResult = file_get_contents($configFilename);
|
98 |
-
if ($configLoadResult === false) {
|
99 |
-
throw new ValidateException('Cannot open config file');
|
100 |
-
}
|
101 |
-
$json = SanityCheck::isJSONObject($configLoadResult);
|
102 |
-
|
103 |
-
$options = json_decode($json, true);
|
104 |
-
$wodOptions = $options['wod'];
|
105 |
-
$serveOptions = $options['webp-convert'];
|
106 |
-
$convertOptions = &$serveOptions['convert'];
|
107 |
-
//echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
|
108 |
-
|
109 |
-
|
110 |
-
// Validate that WebPExpress was configured to redirect to this conversion script
|
111 |
-
// (but do not require that for Nginx)
|
112 |
-
// ------------------------------------------------------------------------------
|
113 |
-
$checking = 'settings';
|
114 |
-
if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
115 |
-
if (!isset($wodOptions['enable-redirection-to-converter']) || ($wodOptions['enable-redirection-to-converter'] === false)) {
|
116 |
-
throw new ValidateException('Redirection to conversion script is not enabled');
|
117 |
-
}
|
118 |
-
|
119 |
-
}
|
120 |
-
|
121 |
-
|
122 |
-
// Check source (the image to be converted)
|
123 |
-
// --------------------------------------------
|
124 |
-
$checking = 'source';
|
125 |
-
|
126 |
-
// Check if it is in an environment variable
|
127 |
-
$source = self::getEnvPassedInRewriteRule('REQFN');
|
128 |
-
if ($source !== false) {
|
129 |
-
$checking = 'source (passed through env)';
|
130 |
-
$source = SanityCheck::absPathExistsAndIsFile($source);
|
131 |
-
} else {
|
132 |
-
// Check if it is in header (but only if .htaccess was configured to send in header)
|
133 |
-
if (isset($wodOptions['base-htaccess-on-these-capability-tests'])) {
|
134 |
-
$capTests = $wodOptions['base-htaccess-on-these-capability-tests'];
|
135 |
-
$passThroughHeaderDefinitelyUnavailable = ($capTests['passThroughHeaderWorking'] === false);
|
136 |
-
$passThrougEnvVarDefinitelyAvailable =($capTests['passThroughEnvWorking'] === true);
|
137 |
-
// This determines if .htaccess was configured to send in querystring
|
138 |
-
$headerMagicAddedInHtaccess = ((!$passThrougEnvVarDefinitelyAvailable) && (!$passThroughHeaderDefinitelyUnavailable));
|
139 |
-
} else {
|
140 |
-
$headerMagicAddedInHtaccess = true; // pretend its true
|
141 |
-
}
|
142 |
-
|
143 |
-
if ($headerMagicAddedInHtaccess && (isset($_SERVER['HTTP_REQFN']))) {
|
144 |
-
$checking = 'source (passed through request header)';
|
145 |
-
$source = SanityCheck::absPathExistsAndIsFile($_SERVER['HTTP_REQFN']);
|
146 |
-
} else {
|
147 |
-
// Check querystring (relative path)
|
148 |
-
$srcRel = '';
|
149 |
-
if (isset($_GET['xsource-rel'])) {
|
150 |
-
$checking = 'source (passed as relative path, through querystring)';
|
151 |
-
$xsrcRel = SanityCheck::noControlChars($_GET['xsource-rel']);
|
152 |
-
$srcRel = SanityCheck::pathWithoutDirectoryTraversal(substr($xsrcRel, 1));
|
153 |
-
$source = SanityCheck::absPathExistsAndIsFile($docRoot . '/' . $srcRel);
|
154 |
-
} else {
|
155 |
-
// Then querystring (full path)
|
156 |
-
// - But only on Nginx (our Apache .htaccess rules never passes absolute url)
|
157 |
-
if (
|
158 |
-
(stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) &&
|
159 |
-
(isset($_GET['source']) || isset($_GET['xsource']))
|
160 |
-
) {
|
161 |
-
$checking = 'source (passed as absolute path on nginx)';
|
162 |
-
if (isset($_GET['source'])) {
|
163 |
-
$source = SanityCheck::absPathExistsAndIsFile($_GET['source']);
|
164 |
-
} else {
|
165 |
-
$xsrc = SanityCheck::noControlChars($_GET['xsource']);
|
166 |
-
$source = SanityCheck::absPathExistsAndIsFile(substr($xsrc, 1));
|
167 |
-
}
|
168 |
-
} else {
|
169 |
-
// Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
|
170 |
-
// correct result in all setups (ie "folder method 1")
|
171 |
-
$checking = 'source (retrieved by the request_uri server var)';
|
172 |
-
$srcRel = SanityCheck::pathWithoutDirectoryTraversal(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
|
173 |
-
$source = SanityCheck::absPathExistsAndIsFile($docRoot . $srcRel);
|
174 |
-
}
|
175 |
-
}
|
176 |
-
}
|
177 |
-
}
|
178 |
-
|
179 |
-
// Make sure it is in doc root
|
180 |
-
$source = SanityCheck::absPathIsInDocRoot($source);
|
181 |
-
|
182 |
-
// Check destination path
|
183 |
-
// --------------------------------------------
|
184 |
-
$checking = 'destination path';
|
185 |
-
$destination = ConvertHelperIndependent::getDestination(
|
186 |
-
$source,
|
187 |
-
$wodOptions['destination-folder'],
|
188 |
-
$wodOptions['destination-extension'],
|
189 |
-
$webExpressContentDirAbs,
|
190 |
-
$docRoot . '/' . $wodOptions['paths']['uploadDirRel']
|
191 |
-
);
|
192 |
-
//echo 'dest:' . $destination; exit;
|
193 |
-
$destination = SanityCheck::absPathIsInDocRoot($destination);
|
194 |
-
$destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Does not end with .webp');
|
195 |
-
|
196 |
-
} catch (SanityException $e) {
|
197 |
-
self::exitWithError('Sanity check failed for ' . $checking . ': '. $e->getMessage());
|
198 |
-
} catch (ValidateException $e) {
|
199 |
-
self::exitWithError('Validation failed for ' . $checking . ': '. $e->getMessage());
|
200 |
-
}
|
201 |
-
|
202 |
-
// Done with sanitizing, lets get to work!
|
203 |
-
// ---------------------------------------
|
204 |
-
if (isset($wodOptions['success-response']) && ($wodOptions['success-response'] == 'original')) {
|
205 |
-
$serveOptions['serve-original'] = true;
|
206 |
-
$serveOptions['serve-image']['headers']['vary-accept'] = false;
|
207 |
-
} else {
|
208 |
-
$serveOptions['serve-image']['headers']['vary-accept'] = true;
|
209 |
-
}
|
210 |
-
//echo $source . '<br>' . $destination; exit;
|
211 |
-
|
212 |
-
ConvertHelperIndependent::serveConverted(
|
213 |
-
$source,
|
214 |
-
$destination,
|
215 |
-
$serveOptions,
|
216 |
-
$webExpressContentDirAbs . '/log',
|
217 |
-
'Conversion triggered with the conversion script (wod/webp-on-demand.php)'
|
218 |
-
);
|
219 |
-
}
|
220 |
-
}
|
221 |
|
222 |
// Protect against directly accessing webp-on-demand.php
|
223 |
// Only protect on Apache. We know for sure that the method is not reliable on nginx. We have not tested on litespeed yet, so we dare not.
|
@@ -230,4 +13,13 @@ if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER
|
|
230 |
}
|
231 |
}
|
232 |
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<?php
|
2 |
|
3 |
+
use \WebPExpress\WebPOnDemand;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
// Protect against directly accessing webp-on-demand.php
|
6 |
// Only protect on Apache. We know for sure that the method is not reliable on nginx. We have not tested on litespeed yet, so we dare not.
|
13 |
}
|
14 |
}
|
15 |
|
16 |
+
define('WOD_DIR', __DIR__);
|
17 |
+
|
18 |
+
function webpexpress_autoloader($class) {
|
19 |
+
if (strpos($class, 'WebPExpress\\') === 0) {
|
20 |
+
require_once WOD_DIR . '/../lib/classes/' . substr($class, 12) . '.php';
|
21 |
+
}
|
22 |
+
}
|
23 |
+
spl_autoload_register('webpexpress_autoloader');
|
24 |
+
|
25 |
+
WebPOnDemand::processRequest();
|
wod/webp-realizer.php
CHANGED
@@ -1,220 +1,25 @@
|
|
1 |
<?php
|
2 |
-
namespace WebPExpress;
|
3 |
|
4 |
-
use \
|
5 |
-
use \WebPConvert\Serve\ServeConvertedWebP;
|
6 |
-
use \WebPExpress\ConvertHelperIndependent;
|
7 |
-
use \WebPExpress\Sanitize;
|
8 |
-
use \WebPExpress\SanityCheck;
|
9 |
-
use \WebPExpress\SanityException;
|
10 |
-
use \WebPExpress\ValidateException;
|
11 |
-
use \WebPExpress\Validate;
|
12 |
-
|
13 |
-
class WebPRealizer
|
14 |
-
{
|
15 |
-
|
16 |
-
private static $docRoot;
|
17 |
-
|
18 |
-
public static function exitWithError($msg) {
|
19 |
-
header('X-WebP-Express-Error: ' . $msg, true);
|
20 |
-
echo $msg;
|
21 |
-
exit;
|
22 |
-
}
|
23 |
-
|
24 |
-
/**
|
25 |
-
* Get environment variable set with mod_rewrite module
|
26 |
-
* Return false if the environment variable isn't found
|
27 |
-
*/
|
28 |
-
private static function getEnvPassedInRewriteRule($envName) {
|
29 |
-
// Envirenment variables passed through the REWRITE module have "REWRITE_" as a prefix (in Apache, not Litespeed, if I recall correctly)
|
30 |
-
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
31 |
-
// Multiple iterations causes multiple REWRITE_ prefixes, and we get many environment variables set.
|
32 |
-
// We simply look for an environment variable that ends with what we are looking for.
|
33 |
-
// (so make sure to make it unique)
|
34 |
-
$len = strlen($envName);
|
35 |
-
foreach ($_SERVER as $key => $item) {
|
36 |
-
if (substr($key, -$len) == $envName) {
|
37 |
-
return $item;
|
38 |
-
}
|
39 |
-
}
|
40 |
-
return false;
|
41 |
-
}
|
42 |
-
|
43 |
-
public static function process() {
|
44 |
-
|
45 |
-
include_once __DIR__ . "/../lib/classes/ConvertHelperIndependent.php";
|
46 |
-
include_once __DIR__ . '/../lib/classes/Sanitize.php';
|
47 |
-
include_once __DIR__ . '/../lib/classes/SanityCheck.php';
|
48 |
-
include_once __DIR__ . '/../lib/classes/SanityException.php';
|
49 |
-
include_once __DIR__ . '/../lib/classes/Validate.php';
|
50 |
-
include_once __DIR__ . '/../lib/classes/ValidateException.php';
|
51 |
-
|
52 |
-
// Check input
|
53 |
-
// --------------
|
54 |
-
|
55 |
-
try {
|
56 |
-
|
57 |
-
// Check DOCUMENT_ROOT
|
58 |
-
// ----------------------
|
59 |
-
$checking = 'DOCUMENT_ROOT';
|
60 |
-
$docRoot = SanityCheck::absPath($_SERVER["DOCUMENT_ROOT"]);
|
61 |
-
|
62 |
-
// Use realpath to expand symbolic links and check if it exists
|
63 |
-
$docRoot = realpath($docRoot);
|
64 |
-
if ($docRoot === false) {
|
65 |
-
throw new SanityException('Cannot find document root');
|
66 |
-
}
|
67 |
-
$docRoot = rtrim($docRoot, '/');
|
68 |
-
$docRoot = SanityCheck::absPathExistsAndIsDir($docRoot);
|
69 |
-
|
70 |
-
// Check wp-content
|
71 |
-
// ----------------------
|
72 |
-
|
73 |
-
// Passed in env variable?
|
74 |
-
$wpContentDirRel = self::getEnvPassedInRewriteRule('WPCONTENT');
|
75 |
-
if ($wpContentDirRel === false) {
|
76 |
-
|
77 |
-
// Passed in QS?
|
78 |
-
if (isset($_GET['wp-content'])) {
|
79 |
-
$wpContentDirRel = SanityCheck::pathWithoutDirectoryTraversal($_GET['wp-content']);
|
80 |
-
} else {
|
81 |
-
// In case above fails, fall back to standard location
|
82 |
-
$wpContentDirRel = 'wp-content';
|
83 |
-
}
|
84 |
-
}
|
85 |
-
|
86 |
-
// Check WebP Express content dir
|
87 |
-
// ---------------------------------
|
88 |
-
$checking = 'WebP Express content dir';
|
89 |
-
$webExpressContentDirAbs = SanityCheck::absPathExistsAndIsDir($docRoot . '/' . $wpContentDirRel . '/webp-express');
|
90 |
-
|
91 |
-
|
92 |
-
// Check config file name
|
93 |
-
// ---------------------------------
|
94 |
-
$checking = 'config file';
|
95 |
-
$configFilename = SanityCheck::absPathExistsAndIsFileInDocRoot($webExpressContentDirAbs . '/config/wod-options.json');
|
96 |
-
|
97 |
-
|
98 |
-
// Check config file
|
99 |
-
// --------------------
|
100 |
-
$configLoadResult = file_get_contents($configFilename);
|
101 |
-
if ($configLoadResult === false) {
|
102 |
-
throw new ValidateException('Cannot open config file');
|
103 |
-
}
|
104 |
-
$json = SanityCheck::isJSONObject($configLoadResult);
|
105 |
-
|
106 |
-
$options = json_decode($json, true);
|
107 |
-
$wodOptions = $options['wod'];
|
108 |
-
$serveOptions = $options['webp-convert'];
|
109 |
-
$convertOptions = &$serveOptions['convert'];
|
110 |
-
//echo '<pre>' . print_r($wodOptions, true) . '</pre>'; exit;
|
111 |
-
|
112 |
-
|
113 |
-
// Validate that WebPExpress was configured to redirect to this conversion script
|
114 |
-
// (but do not require that for Nginx)
|
115 |
-
// ------------------------------------------------------------------------------
|
116 |
-
$checking = 'settings';
|
117 |
-
if (stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
118 |
-
if (!isset($wodOptions['enable-redirection-to-webp-realizer']) || ($wodOptions['enable-redirection-to-webp-realizer'] === false)) {
|
119 |
-
throw new ValidateException('Redirection to webp realizer is not enabled');
|
120 |
-
}
|
121 |
-
}
|
122 |
-
|
123 |
-
|
124 |
-
// Check destination (the image that was requested, but has not been converted yet)
|
125 |
-
// ------------------------------------------------------------------------------------
|
126 |
-
$checking = 'destination path';
|
127 |
-
|
128 |
-
// Check if it is in an environment variable
|
129 |
-
$destRel = self::getEnvPassedInRewriteRule('DESTINATIONREL');
|
130 |
-
if ($destRel !== false) {
|
131 |
-
$destination = SanityCheck::absPath($docRoot . '/' . $destRel);
|
132 |
-
} else {
|
133 |
-
// Check querystring (relative path)
|
134 |
-
if (isset($_GET['xdestination-rel'])) {
|
135 |
-
$xdestRel = SanityCheck::noControlChars($_GET['xdestination-rel']);
|
136 |
-
$destRel = SanityCheck::pathWithoutDirectoryTraversal(substr($xdestRel, 1));
|
137 |
-
$destination = SanityCheck::absPath($docRoot . '/' . $destRel);
|
138 |
-
} else {
|
139 |
-
|
140 |
-
// Then querystring (full path)
|
141 |
-
// - But only on Nginx (our Apache .htaccess rules never passes absolute url)
|
142 |
-
if (
|
143 |
-
(stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') !== false) &&
|
144 |
-
(isset($_GET['destination']) || isset($_GET['xdestination']))
|
145 |
-
) {
|
146 |
-
if (isset($_GET['destination'])) {
|
147 |
-
$destination = SanityCheck::absPathIsInDocRoot($_GET['destination']);
|
148 |
-
} else {
|
149 |
-
$xdest = SanityCheck::noControlChars($_GET['xdestination']);
|
150 |
-
$destination = SanityCheck::absPathIsInDocRoot(substr($xdest, 1));
|
151 |
-
}
|
152 |
-
} else {
|
153 |
-
// Last resort is to use $_SERVER['REQUEST_URI'], well knowing that it does not give the
|
154 |
-
// correct result in all setups (ie "folder method 1")
|
155 |
-
$destRel = SanityCheck::pathWithoutDirectoryTraversal(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
|
156 |
-
$destination = SanityCheck::absPath($docRoot . $destRel);
|
157 |
-
}
|
158 |
-
}
|
159 |
-
}
|
160 |
-
|
161 |
-
$destination = SanityCheck::pregMatch('#\.webp$#', $destination, 'Does not end with .webp');
|
162 |
-
$destination = SanityCheck::absPathIsInDocRoot($destination);
|
163 |
-
|
164 |
-
|
165 |
-
// Validate source path
|
166 |
-
// --------------------------------------------
|
167 |
-
$checking = 'source path';
|
168 |
-
$source = ConvertHelperIndependent::findSource(
|
169 |
-
$destination,
|
170 |
-
$wodOptions['destination-folder'],
|
171 |
-
$wodOptions['destination-extension'],
|
172 |
-
$webExpressContentDirAbs
|
173 |
-
);
|
174 |
-
|
175 |
-
if ($source === false) {
|
176 |
-
header('X-WebP-Express-Error: webp-realizer.php could not find an existing jpg/png that corresponds to the webp requested', true);
|
177 |
-
|
178 |
-
$protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : 'HTTP/1.0';
|
179 |
-
header($protocol . " 404 Not Found");
|
180 |
-
die();
|
181 |
-
//echo 'destination requested:<br><i>' . $destination . '</i>';
|
182 |
-
}
|
183 |
-
$source = SanityCheck::absPathExistsAndIsFileInDocRoot($source);
|
184 |
-
|
185 |
-
} catch (SanityException $e) {
|
186 |
-
self::exitWithError('Sanity check failed for ' . $checking . ': '. $e->getMessage());
|
187 |
-
} catch (ValidateException $e) {
|
188 |
-
self::exitWithError('Validation failed for ' . $checking . ': '. $e->getMessage());
|
189 |
-
}
|
190 |
-
|
191 |
-
|
192 |
-
// Done with sanitizing, lets get to work!
|
193 |
-
// ---------------------------------------
|
194 |
-
$serveOptions['add-vary-header'] = false;
|
195 |
-
$serveOptions['fail'] = '404';
|
196 |
-
$serveOptions['fail-when-fail-fails'] = '404';
|
197 |
-
$serveOptions['serve-image']['headers']['vary-accept'] = false;
|
198 |
-
|
199 |
-
ConvertHelperIndependent::serveConverted(
|
200 |
-
$source,
|
201 |
-
$destination,
|
202 |
-
$serveOptions,
|
203 |
-
$webExpressContentDirAbs . '/log',
|
204 |
-
'Conversion triggered with the conversion script (wod/webp-realizer.php)'
|
205 |
-
);
|
206 |
-
}
|
207 |
-
}
|
208 |
|
209 |
// Protect against directly accessing webp-on-demand.php
|
210 |
// Only protect on Apache. We know for sure that the method is not reliable on nginx. We have not tested on litespeed yet, so we dare not.
|
211 |
if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
212 |
if (strpos($_SERVER['REQUEST_URI'], 'webp-realizer.php') !== false) {
|
213 |
-
|
214 |
'It seems you are visiting this file (plugins/webp-express/wod/webp-realizer.php) directly. We do not allow this.'
|
215 |
);
|
216 |
exit;
|
217 |
}
|
218 |
}
|
219 |
|
220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<?php
|
|
|
2 |
|
3 |
+
use \WebPExpress\WebPRealizer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
// Protect against directly accessing webp-on-demand.php
|
6 |
// Only protect on Apache. We know for sure that the method is not reliable on nginx. We have not tested on litespeed yet, so we dare not.
|
7 |
if (stripos($_SERVER["SERVER_SOFTWARE"], 'apache') !== false && stripos($_SERVER["SERVER_SOFTWARE"], 'nginx') === false) {
|
8 |
if (strpos($_SERVER['REQUEST_URI'], 'webp-realizer.php') !== false) {
|
9 |
+
WebPOnDemand::exitWithError(
|
10 |
'It seems you are visiting this file (plugins/webp-express/wod/webp-realizer.php) directly. We do not allow this.'
|
11 |
);
|
12 |
exit;
|
13 |
}
|
14 |
}
|
15 |
|
16 |
+
define('WOD_DIR', __DIR__);
|
17 |
+
|
18 |
+
function webpexpress_autoloader($class) {
|
19 |
+
if (strpos($class, 'WebPExpress\\') === 0) {
|
20 |
+
require_once WOD_DIR . '/../lib/classes/' . substr($class, 12) . '.php';
|
21 |
+
}
|
22 |
+
}
|
23 |
+
spl_autoload_register('webpexpress_autoloader');
|
24 |
+
|
25 |
+
WebPRealizer::processRequest();
|