Test on multisite
rosell-dk opened this issue · comments
when will this point be back at roadmap?
your plugin sounds great but we have a multisite system. is there an option i can activate/deactivate to activate the plugin at multisite for selftest?
So it was noticed, that I took it off the roadmap :)
Perhaps I should put it back on... It might actually not be too hard to make it work multisite.
Currently the plugin will immediately deactivate, if it detects it runs in multi site configuration. The only reason I do this, is because I have not tested multi site AT ALL.
To remove that behaviour, you must edit one of the plugin files manually.
In plugins/webp-express/lib/activate-first-time.php, remove the following code:
if ( is_multisite() ) {
Messenger::addMessage('error', 'You are on multisite. It is not supported yet. BUT IT IS ON THE ROADMAP! Stay tuned! The plugin has been <i>deactivated</i> again!');
Actions::procastinate('deactivate');
return;
}
I look forward to hear how it goes...
It works on the main site, doesn't seem to work on "child" sites. Might me just me though, I'm using nginx and maybe the routing is just off.
Works fine for me so far, if the plugin is not network activated, but activated on the child website independently.
@feeltheice77, @ktmn and @portalzine: I have multisite functionality in beta.
Will you help testing it?
To test it, you must:
- Go to https://wordpress.org/plugins/webp-express/advanced/
- Scroll down to the “Previous versions” section (in the bottom of the page)
- Select “Development version” and click “Download”
- Install manually
Please report test results (good or bad).
Related threads / topics / issues:
#173
https://wordpress.org/support/topic/multisite-509/#post-11147135
I updated to development version. Network activated it and now the settings page is in network admin and not on any other dashboards. All good there.
The main site serves webps, but the child sites (subdirectory) don't.
For example:
Visiting example.com
the file example.com/wp-content/uploads/2018/01/img.jpg
is served as webp.
Visiting example.com/childsite
the file example.com/childsite/wp-content/uploads/sites/50/2018/03/another.jpg
is not served as webp.
"Direct link/non-wp image" such as example.com/otherassets/logo.png
is correctly served as webp whether it's displayed on example.com
or example.com/childsite
.
My nginx conf has this:
if ($http_accept ~* "webp") {
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source=$document_root$request_uri&wp-content=wp-content&%1 break;
}
Does the source
need to be changed?
The source parameter in Query String is currently only used when the "Do not pass source in Query String" is disabled, which currently only can be done in Tweaked mode. I'm considering changing that behaviour because it seems that option to pass source in QS is needed in more cases than I thought.
To try if it works with source, you can switch to Tweaked mode and disable that option.
Or you can modify "wod/webp-on-demand.php"
Look for this code:
if ($allowInQS) {
if (isset($_GET['xsource'])) {
return substr($_GET['xsource'], 1); // No url decoding needed as $_GET is already decoded
} elseif (isset($_GET['source'])) {
return $_GET['source'];
}
}
change it to:
if (isset($_GET['xsource'])) {
return substr($_GET['xsource'], 1); // No url decoding needed as $_GET is already decoded
} elseif (isset($_GET['source'])) {
return $_GET['source'];
}
I haven't tested multisite with Nginx. Forgot about that. What are the nginx configuration rules that you use for Multisite?
What are the nginx configuration rules that you use for Multisite?
Using the Nginx Helper plugin, the multisite gist of it is this:
map $uri $blogname{
~^(?<blogpath>/[^/]+/)files/(.*) $blogpath ;
}
map $blogname $blogid{
default -999;
include /var/www/example.com/htdocs/wp-content/plugins/nginx-helper/map.conf ;
}
server{ ## inside server block
location ~ ^(/[^/]+/)?files/(?<rt_file>.+) {
try_files /wp-content/blogs.dir/$blogid/files/$rt_file /wp-includes/ms-files.php?file=$rt_file ;
access_log off; log_not_found off; expires max;
}
}
Switching to Tweaked mode and commenting out the if ($allowInQS)
condition didn't have any effect afaik.
Yeah, it seems that these rules are for serving static files directly. The rules probably takes the request before our rules gets to process.
Solving this probably requires some trickery.
We should read up on this: https://easyengine.io/wordpress-nginx/tutorials/multisite/static-files-handling
And you should check the updated Nginx rules in the FAQ.
I have other stuff in my hands, but shall return to this
Btw: Does the Alter HTML functionality work ?
Does the Alter HTML functionality work ?
With a custom theme (Sage based) mostly not - only image in widget area was converted (created with Monster Widget), but not image block in post content. But with 2019 seemed like it worked everywhere, but started getting 404's for WC product page images, so not compatible with that I don't think.
As for the nginx config, this is more similar to the conf I'm using.
It's got this part for multisite:
if (!-e $request_filename) {
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
rewrite ^(/[^/]+)?(/wp-.*) $2 last;
rewrite ^/[^/]+(/.*.php)$ $1 last;
}
and webp code I added after it:
if ($http_accept ~* "webp") {
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source=$document_root$request_uri&wp-content=wp-content&%1 break;
}
The rewrite ^(/[^/]+)?(/wp-.*) $2 last;
line rewrote child site urls and it never reached webp-on-demand.php
.
So I moved the webp part above it and now the issue was with the source
variable. The nginx $document_root$request_uri
includes the child site name in the uri, while in the filesystem it's path doesn't include it.
Bad: /var/www/example.com/public_html/childsite/wp-content/uploads/sites/50/2018/03/another.jpg
Good: /var/www/example.com/public_html/wp-content/uploads/sites/50/2018/03/another.jpg
Once I added some PHP to strip out the childsite
from the path it works great for me. I wonder if there's a proper way to do it with nginx or not, but other than that, a filter for $source
before this:
if (!file_exists($source)) {
header('X-WebP-Express-Error: Source file not found!', true);
echo 'Source file not found!';
exit;
}
would do the trick too.
I cannot add filter because webp-on-demand. php does not run Wordpress init.
I'm thinking that I for NGINX multisite could allow webp-on-demand.php to do some heuristics
webp-on-demand.php
knows the path to "wp-content", as the path is provided in the querystring (as a relative path between document root and "wp-content"). So it knows that images are to be found in that path.
Ie:
/var/www/example.com/public_html/wp-content
So when it receives "BAD" (works for GOOD as well):
var/www/example.com/public_html/childsite/wp-content/uploads/sites/50/2018/03/another.jpg
It could first subtract document root:
/childsite/wp-content/uploads/sites/50/2018/03/another.jpg
and then it could skip folders until it reaches "wp-content":
/wp-content/uploads/sites/50/2018/03/another.jpg
and then it could append it to document root:
var/www/example.com/public_html/wp-content/uploads/sites/50/2018/03/another.jpg
Perhaps only for NGINX and multisite
I can easily have webp-on-demand.php know that we are running in multisite, by storing that fact in the config (wod-options.json
) - or by requiring it to be passed in the query string.
The method could perhaps also be used for Apache sites. Perhaps optionally
Another approach could be to try to come up with a rewrite rule that inserts the path from the matched path instead of the server variables. The path will be relative, so webp-on-demand.php must then accept relative paths too. Something like this:
if ($http_accept ~* "webp") {
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source-rel=$1.$2&wp-content=wp-content&%1 break;
}
I'm not sure if that will match the subsite as well. In case it does, one could do something like this:
if ($http_accept ~* "webp") {
rewrite ^/(childsite|childsite2)(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source-rel=$2.$3&wp-content=wp-content&%1 break;
}
I'm not good enough with nginx or regex to make it work in server config.
if ($http_accept ~* "webp") {
rewrite ^/(childsite|childsite2)(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php?source-rel=$2.$3&wp-content=wp-content&%1 break;
}
Experiemented with this a little, couldn't make it work.
The $1
, $2
and $3
will be different things for main site and child sites, and the (childsite|childsite2)
part matches childsite
for /childsite2
and $2
ends up with 2/wp-content/...
.
In my earlier comment there should be a way to get the actual child site name, but it's meant for older versions of WPMU I think and doesn't work for me.
I don't know what the best way would be for different setups but for me, this could work:
On a child site
the $document_root
is /var/www/example.com/public_html
and $request_uri
is /childsite/wp-content/uploads/sites/50/2018/03/another.jpg
What about passing webp-on-demand.php?source-root=$document_root&source-uri=$request_uri&wp-content=wp-content&%1
Then webp-on-demand.php
would need to check that the first URI part of $_GET['source-uri']
matches $_GET['wp-content']
and if it doesn't, then remove the parts that come before it, and then put the root and uri together for the source.
Something like this:
if(isset($_GET['source-root']) && isset($_GET['source-uri'])) {
$wp_content = isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content';
$parts = explode('/', $_GET['source-uri']);
foreach($parts as $index => $part) {
if($part !== $wp_content) {
unset($parts[$index]);
} else {
break;
}
}
$source = $_GET['source-root'] . '/' . implode('/', $parts);
}
I cannot add filter because webp-on-demand. php does not run Wordpress init.
When push comes to shove you could do a non-wordpress filter, like look for a specifically named file in $_SERVER['DOCUMENT_ROOT']
and include it if it exists, and then apply a specifically named function from there.
Both ideas sounds good...
It seems there is no reason to pass source-root, as document root should be the same in nginx conf and in the script.
Even before this, I had been thinking about introducing "source-rel" (relative path from document root to source file). Passing the complete path increases the risk of problems of firewall blocking. I think I'll go with "source-rel" rather than "source-uri".
I think implementing the filter in the script, and activating it by name through the QS will do. It is simpler and doesn't require any security measures. Ie:
?source-rel=$request_uri&source-rel-filter=discard-first-folder
Sounds good, as long as discard-first-folder
doesn't discard the first folder when it's already wp-content
(parent site).
I see. discard-parts-before-wp-content
would be a better name, then.
This stuff doesn't work when upload folder has been moved or wp-content folder has been moved (not just renamed), but I guess it solves the needs of the majority of the minority that has these kinds of needs.
I have added the option to discard parts before wp-content. To test, please update to master. Note that the vendor library isn't distributed here on github, so make sure to copy the vendor folder over.
There is now a new option "Method for passing filename" in the "Redirect rules" section. You must select "Pass through query string (relative)"
I just discovered that you were using ?source=$document_root$request_uri
rather than ?source=$request_filename
, which I currently recommend in the FAQ.
Perhaps changing that solves it so we don't need the filter after all?
source-rel=$request_uri&source-rel-filter=discard-parts-before-wp-content
with Pass through query string (relative path)
enabled works with master.
Except it broke static/non-wp images, by looking for wp-content
where there was none. Slight alteration to the code fixed it:
if (isset($_GET['source-rel-filter'])) {
$parts = explode('/', $srcRel);
$wp_content = isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content';
if ($_GET['source-rel-filter'] == 'discard-parts-before-wp-content' && in_array($wp_content, $parts)) {
foreach($parts as $index => $part) {
if($part !== $wp_content) {
unset($parts[$index]);
} else {
break;
}
}
$srcRel = implode('/', $parts);
}
}
(Moved $parts
and $wp_content
declarations up and added in_array($wp_content, $parts)
check).
I just discovered that you were using
?source=$document_root$request_uri
rather than?source=$request_filename
, which I currently recommend in the FAQ.
Perhaps changing that solves it so we don't need the filter after all?
source=$request_filename
gave the same value as source=$document_root$request_uri
.
The value seems to be dynamic based on rewrites and whatnot. Logging it at the "end":
location ~\.php$ {
...
add_header X-filename "$request_filename" always;
}
the value ends up as /var/www/html/example.com/public_html/wp-content/plugins/webp-express/wod/webp-on-demand.php
by the
if ($http_accept ~* "webp") {
rewrite ^/(.*).(jpe?g|png)$ /wp-content/plugins/webp-express/wod/webp-on-demand.php... break;
}
block, while in there it's still $document_root$request_uri
, so to get the "correct" $request_filename
I think I would need to move the webp block back below this block multisite:
if (!-e $request_filename) {
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
rewrite ^(/[^/]+)?(/wp-.*) $2 last;
rewrite ^(/[^/]+)?(/.*\.php) $2 last;
}
but I'm not quite sure how it works and the last
parameter makes it never reach the webp block on child sites (if that makes any sense). It's probably possible but not with my setup/knowledge.
webp-on-demand.php security:
I'm just wondering, how safe is it to use the $_GET
variables in webp-on-demand.php
"as is"? Could someone navigate to example.com/wp-content/plugins/webp-express/wod/wod-on-demand.php
with some fishy values for the query strings and get something out of it?
Probably not, but for peace of mind, would it be a valid opt-in security measure for webp-on-demand.php
's first line to check for some sort of $_GET['password']
and the server config to include it as a parameter:
if ($http_accept ~* "webp") {
rewrite ^/(.*).(jpe?g|png)$ .../webp-on-demand.php?password=asd123... break;
}
or would that leak somewhere in the serving process? Delivering the correct password to check against to webp-on-demand.php
is probably also a challenge, but something to think about I suppose.
Altered your change a bit, so the code for 'discard-parts-before-wp-content' filter is contained inside the
if ($_GET['source-rel-filter'] == 'discard-parts-before-wp-content')
:
if (isset($_GET['source-rel-filter'])) {
if ($_GET['source-rel-filter'] == 'discard-parts-before-wp-content') {
$parts = explode('/', $srcRel);
$wp_content = isset($_GET['wp-content']) ? $_GET['wp-content'] : 'wp-content';
if (in_array($wp_content, $parts)) {
foreach($parts as $index => $part) {
if($part !== $wp_content) {
unset($parts[$index]);
} else {
break;
}
}
$srcRel = implode('/', $parts);
}
}
}
Yes security. I have also wondered if the script could be misused somehow...
Actually, denying direct calls to webp-on-demand.php seems to be easily achieved in Nginx with the "internal" directive: https://nginx.org/en/docs/http/ngx_http_core_module.html#internal
Nice, thanks!
I tried inserting this:
location ~ webp-on-demand\.php$ {
internal;
}
It results in a 404 when webp-on-demand is requested directly.
However, it then serves the php source code instead of invoking it when an image is requested
Perhaps a password is the simplest solution. It could be stored in the configuration file as well as be handed over in the query string. The query string cannot be detected because the redirect is internal (both on Nginx and in Apache).
The password could be autogenerated. Nginx users, who will need to know it, would be able to view it by opening wp-content/webp-express/config/config.php
. The wp-content/webp-express/config
folder is protected from the outside by the .htaccess file in it. A similar protection must be done in Nginx
Opening a new issue for that
It seems we can just insert these lines in the beginning of webp-on-demand.php
:
if (preg_match('#webp-on-demand.php#', $_SERVER['REQUEST_URI'])) {
echo 'Direct access is not allowed';
exit;
}
I'm hoping to release 0.12.0 tomorrow...
Tomorrow from now ;)
@ktmn @rosell-dk I tried to run webp express on multisite (subdir mode) with configs from FAQ, but it doesn't work. What I should add to nginx config?
Any news on this one? I did read through this issue, but I did not find the proper nginx rules for multisite install (in my case subdomain, but that should make any difference I guess)
All I got is this one rule (and it's quite high up in the config file) but I'm using an older version to make it work.
What is your conf, what errors are you getting?
Hi guys, I'm on WP-Engine (which is on NGINX I guess), is there any solution to make the plugin work on child sites? I use sub-directories.
I second that– would be great to be able to install this plugin WITHOUT having to network activate it– I.E. just use it on a child site