The advanced "Choose filters" search (pwg.images.filteredSearch.create) fails with HTTP 400 whenever friendly URLs are enabled ($conf['question_mark_in_urls'] = false). The quick search bar and tag browsing work fine — only filteredSearch is affected. Reproducible on two independent installs and two themes.
The underlying cause appears to be a relative webservice URL: the search UI calls ws.php relative to the current page, so under friendly URLs the request is misrouted to index.php instead of reaching the ws.php webservice.
Steps to reproduce
Set $conf['question_mark_in_urls'] = false; (via LocalFiles Editor / local/config/config.inc.php).
Open the gallery and use the advanced search "Choose filters".
Add any filter (a word or a tag) and validate.
Expected
The search runs and returns matching photos (the request reaches the ws.php webservice and returns JSON).
Actual
The filter panel shows "Error" and returns nothing. The failing request, from the browser network inspector:
Response Content-Type:text/html — the response is an HTML "redirection" page (<title>redirection</title>, <meta http-equiv="refresh" content="5;url=/">), not webservice JSON.
The server-side trace (confirmed by my host) is that filteredSearch.create reaches the application with an empty body, so bad_request('search identifier is missing') is raised.
Likely cause
The request URL is index.php/search/ws.php, i.e. ws.php appended as PATH_INFO to index.php rather than the ws.php webservice at the site root. This matches a webservice call built relative to the current page: with question_mark_in_urls = false the search page is index.php/search/<search_id> (PATH_INFO), so a relative ws.php resolves to index.php/search/ws.php. That request is served by index.php (which returns the HTML redirection page + 400); the filteredSearch POST never reaches the webservice, hence the "empty body" / "search identifier is missing" server-side.
The HTML response body (Piwigo redirection page, not JSON) and the request Content-Length: 358 together confirm the payload is sent by the client but the endpoint is misrouted.
A fix would be to build the filteredSearch webservice URL from the gallery/webservice root rather than relative to the current page (so it resolves to the root ws.php regardless of the URL style).
Independent reproduction
Same endpoint, same 400, friendly URLs, Bootstrap Darkroom theme, Piwigo 15.6.0 (POST body there visibly contains search_id and allwords, yet the server reports the identifier missing): https://piwigo.org/forum/viewtopic.php?pid=193063
(My install: Piwigo 16.4.0, Modus theme. Same 400 in both Modus and Bootstrap Darkroom — theme-independent.)
Workaround and its cost
Setting $conf['question_mark_in_urls'] = true restores the search, but is not usable on this setup: it breaks i.php derivative generation (all thumbnails render as "?"). So the search and the derivatives can't both work via this toggle.
Ruled out
Two themes (Modus, Bootstrap Darkroom) — identical 400.
Tag Groups plugin disabled.
Orphan tags purged (~35 clean tags remain).
Compiled / template cache cleared.
No manual modification of core files.
Environment
Piwigo 16.4.0
Theme: Modus (also reproduced on Bootstrap Darkroom)
PHP 8.2
Host: SiteGround — Nginx reverse proxy in front of Apache, Cloudflare in front
$conf['question_mark_in_urls'] = false
## Summary
The advanced "Choose filters" search (`pwg.images.filteredSearch.create`) fails with **HTTP 400** whenever friendly URLs are enabled (`$conf['question_mark_in_urls'] = false`). The quick search bar and tag browsing work fine — only `filteredSearch` is affected. Reproducible on two independent installs and two themes.
The underlying cause appears to be a **relative webservice URL**: the search UI calls `ws.php` relative to the current page, so under friendly URLs the request is misrouted to `index.php` instead of reaching the `ws.php` webservice.
## Steps to reproduce
1. Set `$conf['question_mark_in_urls'] = false;` (via LocalFiles Editor / `local/config/config.inc.php`).
2. Open the gallery and use the advanced search "Choose filters".
3. Add any filter (a word or a tag) and validate.
## Expected
The search runs and returns matching photos (the request reaches the `ws.php` webservice and returns JSON).
## Actual
The filter panel shows "Error" and returns nothing. The failing request, from the browser network inspector:
- **Request URL:** `https://<host>/index.php/search/ws.php?format=json&method=pwg.images.filteredSearch.create`
- **Status:** 400
- **Referer:** `https://<host>/index.php/search/<search_id>` (e.g. `psk-20260618-...`)
- **Request `Content-Length`:** 358 (the POST body *is* sent by the browser)
- **Request `Content-Type`:** `application/x-www-form-urlencoded`
- **Response `Content-Type`:** `text/html` — the response is an HTML "redirection" page (`<title>redirection</title>`, `<meta http-equiv="refresh" content="5;url=/">`), **not** webservice JSON.
The server-side trace (confirmed by my host) is that `filteredSearch.create` reaches the application with an empty body, so `bad_request('search identifier is missing')` is raised.
## Likely cause
The request URL is `index.php/search/ws.php`, i.e. `ws.php` appended as PATH_INFO to **index.php** rather than the `ws.php` webservice at the site root. This matches a webservice call built **relative to the current page**: with `question_mark_in_urls = false` the search page is `index.php/search/<search_id>` (PATH_INFO), so a relative `ws.php` resolves to `index.php/search/ws.php`. That request is served by `index.php` (which returns the HTML redirection page + 400); the `filteredSearch` POST never reaches the webservice, hence the "empty body" / "search identifier is missing" server-side.
The HTML response body (Piwigo redirection page, not JSON) and the request `Content-Length: 358` together confirm the payload is sent by the client but the endpoint is misrouted.
A fix would be to build the `filteredSearch` webservice URL from the gallery/webservice root rather than relative to the current page (so it resolves to the root `ws.php` regardless of the URL style).
## Independent reproduction
Same endpoint, same 400, friendly URLs, **Bootstrap Darkroom** theme, **Piwigo 15.6.0** (POST body there visibly contains `search_id` and `allwords`, yet the server reports the identifier missing):
https://piwigo.org/forum/viewtopic.php?pid=193063
(My install: Piwigo 16.4.0, Modus theme. Same 400 in both Modus and Bootstrap Darkroom — theme-independent.)
## Workaround and its cost
Setting `$conf['question_mark_in_urls'] = true` restores the search, but is not usable on this setup: it breaks `i.php` derivative generation (all thumbnails render as "?"). So the search and the derivatives can't both work via this toggle.
## Ruled out
- Two themes (Modus, Bootstrap Darkroom) — identical 400.
- Tag Groups plugin disabled.
- Orphan tags purged (~35 clean tags remain).
- Compiled / template cache cleared.
- No manual modification of core files.
## Environment
- Piwigo 16.4.0
- Theme: Modus (also reproduced on Bootstrap Darkroom)
- PHP 8.2
- Host: SiteGround — Nginx reverse proxy in front of Apache, Cloudflare in front
- `$conf['question_mark_in_urls'] = false`
<!-- github-sync-id:4693710724 -->
Quick update from my side. Clean URLs here (question_mark_in_urls = false).
I added a local rewrite to send index.php/search/ws.php to /ws.php, just to
see what happens. With that, filteredSearch.create returns 200 instead of 400
and I do get a search_id back (tiny text/plain response with the id).
But it still breaks right after: the browser goes to index.php/search/psk-…
and that page returns "Bad request: search identifier is missing". So for me
there seem to be two separate problems - the ws.php routing (which I patched on
my end), and then the results page ending up with an id the server says is
missing.
That second part looks like it's happening in the JS, not on the server, so my
rewrite can't touch it. Couldn't read the create response body to confirm
(no-store, gets dropped after the redirect). Anyone know if that's the right
place to look?
Quick update from my side. Clean URLs here (question_mark_in_urls = false).
I added a local rewrite to send index.php/search/ws.php to /ws.php, just to
see what happens. With that, filteredSearch.create returns 200 instead of 400
and I do get a search_id back (tiny text/plain response with the id).
But it still breaks right after: the browser goes to index.php/search/psk-…
and that page returns "Bad request: search identifier is missing". So for me
there seem to be two separate problems - the ws.php routing (which I patched on
my end), and then the results page ending up with an id the server says is
missing.
That second part looks like it's happening in the JS, not on the server, so my
rewrite can't touch it. Couldn't read the create response body to confirm
(no-store, gets dropped after the redirect). Anyone know if that's the right
place to look?
<!-- github-comment-id:4826236919 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
The advanced "Choose filters" search (
pwg.images.filteredSearch.create) fails with HTTP 400 whenever friendly URLs are enabled ($conf['question_mark_in_urls'] = false). The quick search bar and tag browsing work fine — onlyfilteredSearchis affected. Reproducible on two independent installs and two themes.The underlying cause appears to be a relative webservice URL: the search UI calls
ws.phprelative to the current page, so under friendly URLs the request is misrouted toindex.phpinstead of reaching thews.phpwebservice.Steps to reproduce
$conf['question_mark_in_urls'] = false;(via LocalFiles Editor /local/config/config.inc.php).Expected
The search runs and returns matching photos (the request reaches the
ws.phpwebservice and returns JSON).Actual
The filter panel shows "Error" and returns nothing. The failing request, from the browser network inspector:
https://<host>/index.php/search/ws.php?format=json&method=pwg.images.filteredSearch.createhttps://<host>/index.php/search/<search_id>(e.g.psk-20260618-...)Content-Length: 358 (the POST body is sent by the browser)Content-Type:application/x-www-form-urlencodedContent-Type:text/html— the response is an HTML "redirection" page (<title>redirection</title>,<meta http-equiv="refresh" content="5;url=/">), not webservice JSON.The server-side trace (confirmed by my host) is that
filteredSearch.createreaches the application with an empty body, sobad_request('search identifier is missing')is raised.Likely cause
The request URL is
index.php/search/ws.php, i.e.ws.phpappended as PATH_INFO to index.php rather than thews.phpwebservice at the site root. This matches a webservice call built relative to the current page: withquestion_mark_in_urls = falsethe search page isindex.php/search/<search_id>(PATH_INFO), so a relativews.phpresolves toindex.php/search/ws.php. That request is served byindex.php(which returns the HTML redirection page + 400); thefilteredSearchPOST never reaches the webservice, hence the "empty body" / "search identifier is missing" server-side.The HTML response body (Piwigo redirection page, not JSON) and the request
Content-Length: 358together confirm the payload is sent by the client but the endpoint is misrouted.A fix would be to build the
filteredSearchwebservice URL from the gallery/webservice root rather than relative to the current page (so it resolves to the rootws.phpregardless of the URL style).Independent reproduction
Same endpoint, same 400, friendly URLs, Bootstrap Darkroom theme, Piwigo 15.6.0 (POST body there visibly contains
search_idandallwords, yet the server reports the identifier missing):https://piwigo.org/forum/viewtopic.php?pid=193063
(My install: Piwigo 16.4.0, Modus theme. Same 400 in both Modus and Bootstrap Darkroom — theme-independent.)
Workaround and its cost
Setting
$conf['question_mark_in_urls'] = truerestores the search, but is not usable on this setup: it breaksi.phpderivative generation (all thumbnails render as "?"). So the search and the derivatives can't both work via this toggle.Ruled out
Environment
$conf['question_mark_in_urls'] = falseQuick update from my side. Clean URLs here (question_mark_in_urls = false).
I added a local rewrite to send index.php/search/ws.php to /ws.php, just to
see what happens. With that, filteredSearch.create returns 200 instead of 400
and I do get a search_id back (tiny text/plain response with the id).
But it still breaks right after: the browser goes to index.php/search/psk-…
and that page returns "Bad request: search identifier is missing". So for me
there seem to be two separate problems - the ws.php routing (which I patched on
my end), and then the results page ending up with an id the server says is
missing.
That second part looks like it's happening in the JS, not on the server, so my
rewrite can't touch it. Couldn't read the create response body to confirm
(no-store, gets dropped after the redirect). Anyone know if that's the right
place to look?