Fix #148: resolve redirects from the 404 handler instead of a catch-all URL rule - #150
Open
nikolenko-dmitriy wants to merge 1 commit into
Open
Fix #148: resolve redirects from the 404 handler instead of a catch-all URL rule#150nikolenko-dmitriy wants to merge 1 commit into
nikolenko-dmitriy wants to merge 1 commit into
Conversation
…all rule On a multi-site install where a site's base URL carries a URI prefix (e.g. https://example.com/en), redirects stored on that site never fire, and template pages there 404. Both follow from the '<all:.+>' catch-all rule registered on EVENT_REGISTER_SITE_URL_RULES. craft\web\UrlManager::_getRequestRoute() evaluates, in order: token -> element -> URL rules -> .well-known -> template so the catch-all is reached before .well-known and before template routing. The comment on the rule ("Real pages and entries resolve first; only a URL that would otherwise 404 reaches our controller") holds for entries, but not for templates: those resolve last. Consequences: 1. Redirects never fire on a prefixed site. RedirectController passed getFullPath() to resolveForUri(), which keeps the site's URI prefix ("en/foo"), while source URLs are stored site-relative ("foo") and matchUri() compares them directly. Nothing matches. 2. Template pages 404 on a prefixed site, because the controller re-implements the template lookup with the same getFullPath(). 3. A configured .well-known route would be shadowed. Rather than patch getFullPath() -> getPathInfo() in the controller, this stops pre-empting Craft's router: resolution now hooks ErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTION and acts only on a genuine NotFoundHttpException. Craft routes entries, .well-known and templates natively, and only a real 404 reaches the plugin. That is also what the 1.x/2.x line did in effect, via per-source rules. RedirectController is removed (nothing routed to it but the catch-all). EVENT_BEFORE_CATCHALL and FILE_EXTENSIONS move to RedirectPlugin. An unmatched 404 falls through to Craft's own 404 response unless catchAllTemplate is set; the template's `request` payload is unchanged. Verified on Craft 5.10 with a three-site install — one site at the domain root, two under URI prefixes — comparing 5.1.1 against this change with a cold opcache: prefixed site, redirect stored on it 404 -> 301 prefixed site, template page 404 -> 200 root site, template page 200 -> 200 root site, redirect 301 -> 301 unknown path, root and prefixed 404 -> 404 entries, root and prefixed 200 -> 200 Catch-all logging was exercised separately with catchAllActive on: hits log per-site against the site-relative URI, and asset extensions stay excluded. Note: Dolphiq#148 also reports set-password breaking. That is not caused by this rule — craft\web\Request resolves setPasswordPath via _specialPaths against the prefix-stripped path, making it an action request that never reaches the URL rules. It behaved identically before and after here. This change needs the schemaVersion fix to be useful on an existing install, otherwise resolveForUri() hits a missing `priority` column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #148.
Problem
On a multi-site install where a site's base URL carries a URI prefix (e.g.
https://example.com/en), redirects stored on that site never fire, and template pages there 404. The issue reports the template 404s; the dead redirects are the same root cause and arguably the bigger half — the plugin does nothing at all on a prefixed site.Cause
craft\web\UrlManager::_getRequestRoute()evaluates, in order:The
'<all:.+>'catch-all registered onEVENT_REGISTER_SITE_URL_RULESsits in URL rules, so it is reached before.well-knownand before template routing. The comment on the rule — "Real pages and entries resolve first; only a URL that would otherwise 404 reaches our controller" — holds for entries, but templates resolve last. So:RedirectControllerpassedgetFullPath()toresolveForUri(), which keeps the URI prefix (en/foo), while source URLs are stored site-relative (foo) andmatchUri()compares them directly. Nothing matches.getFullPath()..well-knownroute would be shadowed.Fix
Rather than patch
getFullPath()→getPathInfo()in the controller, this stops pre-empting Craft's router altogether: resolution hooksErrorHandler::EVENT_BEFORE_HANDLE_EXCEPTIONand acts only on a genuineNotFoundHttpException. Craft routes entries,.well-knownand templates natively, and only a real 404 reaches the plugin — which is what the 1.x/2.x line did in effect, via per-source rules, and what #148 suggests.RedirectControlleris removed (nothing routed to it but the catch-all).EVENT_BEFORE_CATCHALLandFILE_EXTENSIONSmove toRedirectPlugin. An unmatched 404 falls through to Craft's own 404 response unlesscatchAllTemplateis set; that template'srequestpayload is unchanged.Verification
Craft 5.10, three-site install (one at the domain root, two under URI prefixes), 5.1.1 vs this change, cold opcache:
Catch-all logging exercised separately with
catchAllActiveon: hits log per-site against the site-relative URI, and asset extensions stay excluded.Note on set-password
#148 also reports set-password breaking. That is not caused by this rule —
craft\web\RequestresolvessetPasswordPathvia_specialPathsagainst the prefix-stripped path, making it an action request that never reaches the URL rules. It behaved identically before and after here, so that symptom has another cause.Depends on
Needs the
extra.schemaVersionfix to be useful on an existing install, otherwiseresolveForUri()hits the missingprioritycolumn.🤖 Generated with Claude Code