Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [6.x] Hydrate cascade for error views rendered outside Statamic's exception stack by wakqasahmed · Pull Request #15201 · statamic/cms · GitHub
Skip to content

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack - #15201

Open
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error
Open

[6.x] Hydrate cascade for error views rendered outside Statamic's exception stack#15201
wakqasahmed wants to merge 1 commit into
statamic:6.xfrom
wakqasahmed:fix/issue-14167-cascade-hydration-non-statamic-error

Conversation

@wakqasahmed

Copy link
Copy Markdown
Contributor

Fixes#14167

Problem

Statamic's own HTTP exception classes (Statamic\Exceptions\NotFoundHttpException, ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade via RendersHttpExceptions::contents() before rendering an errors.{status} Antlers template. That's the only place that calls Cascade::instance()->hydrate().

A 404 (or other HTTP error) thrown from outside Statamic's own stack — e.g. Livewire's default 404 handling, which throws Symfony's stock NotFoundHttpException rather than Statamic's subclass — never goes through that path. Laravel falls back to its default exception rendering, which renders the Antlers error template directly with no cascade hydrated. Any {{ global_set_variable }} reference in that template then throws a BadMethodCallException.

The CP route group already gets its own exception handler swapped in via SwapCpExceptionHandler middleware, but the front-end route group has no equivalent, and that middleware only covers Statamic's own exception classes anyway — a generic Symfony exception bypasses it either way since it doesn't define its own render() method.

Fix

Registers a renderable() callback on the app's exception handler (src/Providers/AppServiceProvider.php) that:

  • Skips CP/API routes and JSON requests (already handled elsewhere).
  • Only engages when a matching errors.{status} view exists.
  • Hydrates the cascade and renders the error template through Statamic's View class, the same way RendersHttpExceptions::contents() does.

This only fires for exceptions that don't already define their own render() method (Statamic's own exception classes are checked first by Laravel and never reach this callback), and it's discarded for CP routes since SwapCpExceptionHandler rebinds the exception handler singleton to ControlPanelExceptionHandler, which has its own render() that doesn't consult renderable() callbacks.

Test plan

  • Added it_hydrates_the_cascade_for_a_404_thrown_outside_of_statamics_own_exception_stack to tests/FrontendTest.php, which throws a bare Symfony\Component\HttpKernel\Exception\NotFoundHttpException from an ad-hoc route (mirroring Livewire's default 404 path) with an errors.404 template referencing a global set variable via colon syntax, and asserts it renders successfully.
  • Not run locally — this environment doesn't have a PHP runtime available, so the test is written but unverified here. Written following the existing conventions in tests/FrontendTest.php (FakesViews, withFakeViews/viewShouldReturnRaw) and tests/View/CascadeTest.php (GlobalSet setup). Relying on CI to validate.

… stack (statamic#14167)
Statamic's own HTTP exception classes (NotFoundHttpException,
ForbiddenHttpException, UnauthorizedHttpException) hydrate the cascade
via RendersHttpExceptions::contents() before rendering an Antlers
error template. A generic exception thrown outside that stack (e.g.
Livewire's default 404 handling, which throws Symfony's stock
NotFoundHttpException) never goes through that path, so referencing
a global in the error template throws a BadMethodCallException.
Register a renderable callback on the app's exception handler that
hydrates the cascade and renders the error template through
Statamic's View class whenever a generic HttpExceptionInterface
exception has a matching errors.{status} view, regardless of which
middleware stack or exception class triggered it.
@duncanmccleanduncanmcclean changed the title Hydrate cascade for error views rendered outside Statamic's exception stack (#14167)[6.x] Hydrate cascade for error views rendered outside Statamic's exception stackAug 16, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing globals from non-Statamic 404 page

1 participant

@wakqasahmed