Skip to content

Commit e9bf18e

Browse files
committed
Fix issue with request header casing causing inconsistent behavior between hosting environments
1 parent 75ba0c5 commit e9bf18e

File tree

3 files changed

+49
-15
lines changed

3 files changed

+49
-15
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# FormBuilder HTMX Changelog
22

3+
## 1.0.2 2025-03-01
4+
5+
### Bugfixes, recommended for all users
6+
7+
- Change the way request headers are read to make them case-insensitive. Different hosting
8+
environments may handle request header casing in different ways which was causing inconsistencies
9+
and problems in identifying HTMX FormBuilder requests.
10+
311
## 1.0.1 2024-09-05
412

513
### Bugfixes, new features, recommended for all users
@@ -48,4 +56,4 @@ can be overcome by using FormBuilder HTMX to render all forms.
4856

4957
### Initial release
5058

51-
- Does what README.md says it does
59+
- Does what README.md says it does

FormBuilderHtmx.module.php

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ class FormBuilderHtmx extends Wire implements Module
88
/**
99
* Names of request headers to perist data during per-form request/response
1010
*/
11-
private const ID_REQUEST_HEADER = 'Fb-Htmx-Id';
12-
private const INDICATOR_REQUEST_HEADER = 'Fb-Htmx-Indicator';
11+
private const ID_REQUEST_HEADER = 'fb-htmx-id';
12+
private const INDICATOR_REQUEST_HEADER = 'fb-htmx-indicator';
13+
14+
/**
15+
* Memoized parsed request headers
16+
* @var array
17+
*/
18+
private array $requestHeaders = [];
1319

1420
/**
1521
* Holds the markup for a submitted form if it exists
@@ -104,13 +110,11 @@ private function isHtmxRequest(): bool
104110
{
105111
$input = wire('input');
106112

107-
$requestHeaders = getallheaders() + ['Hx-Request' => false, self::ID_REQUEST_HEADER => null];
108-
109113
return $input->requestMethod('post') &&
110114
$input->_submitKey &&
111115
$input->_InputfieldForm &&
112-
!!$requestHeaders[self::ID_REQUEST_HEADER] &&
113-
$requestHeaders['Hx-Request'] === 'true';
116+
$this->getHeaderValue(self::ID_REQUEST_HEADER, false) &&
117+
$this->getHeaderValue('hx-request', false) === 'true';
114118
}
115119

116120
/**
@@ -192,7 +196,10 @@ private function addFormBuilderHtmxContainer(string $renderedForm, ?string $id =
192196
*/
193197
private function htmxFormId(): string
194198
{
195-
return getallheaders()[self::ID_REQUEST_HEADER] ?? 'fb-htmx-' . (new WireRandom)->alphanumeric(10);
199+
return $this->getHeaderValue(
200+
self::ID_REQUEST_HEADER,
201+
'fb-htmx-' . (new WireRandom)->alphanumeric(10)
202+
);
196203
}
197204

198205
/**
@@ -201,6 +208,26 @@ private function htmxFormId(): string
201208
*/
202209
private function indicatorSelector(?string $indicator = null): ?string
203210
{
204-
return getallheaders()[self::INDICATOR_REQUEST_HEADER] ?? $indicator;
211+
return $this->getHeaderValue(self::INDICATOR_REQUEST_HEADER) ?? $indicator;
212+
}
213+
214+
/**
215+
* Gets a header by name. Is case insensitive to account for potential differences in
216+
* server environments
217+
*
218+
* @param string $headerName Name of header
219+
* @param mixed $default Default value if header does not exist
220+
*/
221+
private function getHeaderValue(string $headerName, mixed $default = null): mixed
222+
{
223+
if (!$this->requestHeaders) {
224+
$requestHeaders = getallheaders();
225+
226+
$this->requestHeaders = array_change_key_case($requestHeaders, CASE_LOWER);
227+
}
228+
229+
$headerName = strtolower($headerName);
230+
231+
return $this->requestHeaders[$headerName] ?? $default;
205232
}
206233
}

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ The `Submit` button is automatically disabled on submission to prevent duplicate
4242

4343
FormBuilderHtmx uses FormBuilder's "Option C: Preferred Method" for rendering. Refer to the 'Embed' tab of your Form Setup page for additional details.
4444

45-
`$htmxForms->render()` is a drop-in replacement for the equivalent FormBuilder method. It can be hooked (details below) and accepts its second `$vars` argument. By drop-in replacement, it allows you to continue using the FormBuilder API as you would expect, including the script and CSS utilities.
45+
`$htmxForms->render()` is a drop-in replacement for the equivalent FormBuilder method. It can be hooked (details below) and accepts its second `$vars` argument. By 'drop-in replacement', it allows you to continue using the FormBuilder API as you would expect, including the scripts and CSS utilities.
4646

4747
```html
4848
<!--
49-
Keep your workflow, $htmxForms returns the same object as $forms
49+
$htmxForms returns the same object as $forms
5050
The second prefill parameter mirrors FormBuilder, add prefill values as needed
5151
-->
5252
$htmxForm = $htmxForms->render('your_form_field', [
@@ -119,8 +119,8 @@ Attempting to add or replace these attributes may lead to issues or cause forms
119119

120120
## CSRF Protection
121121

122-
**CSRF protection may need to be disabled for forms using HTMX/AJAX**
123-
Test to ensure that CSRF errors aren't present when submitting your forms. Disable CSRF if issues are experienced.
122+
**CSRF protection must be disabled for forms using HTMX/AJAX**
123+
Data that the CSRF feature in FormBuilder is not consistent across AJAX requests and submissions will fail with an error. If you are getting odd results or forms that are not working, check that CSRF is disabled before further troubleshooting.
124124

125125
## How Does It Work?
126126

@@ -148,5 +148,4 @@ $wire->addHookAfter('FormBuilderHtmx::render', function(HookEvent $event) {
148148

149149
## Nifty Tricks
150150

151-
This module lets you use the same field multiple times on one page. Only one will be submitted and processed.
152-
151+
This module lets you use the same field multiple times on one page. Only one will be submitted and processed.

0 commit comments

Comments
 (0)