Version 2.5.1

commit d5df9a73f8
Author: Alex Lion <dev@alexandrelion.com>
Date:   Fri May 8 22:42:04 2026 +0200

    Fix form submissions losing values when field names contain spaces or non-word characters

commit 57596952d4
Author: Alex Lion <dev@alexandrelion.com>
Date:   Fri May 8 14:50:33 2026 +0200

    Update package-lock.json: add asset name, upgrade dependencies, and remove unused packages

commit e246530c23
Author: Alex Lion <dev@alexandrelion.com>
Date:   Fri May 8 14:34:53 2026 +0200

    Bump version to 2.5.1
This commit is contained in:
Alex Lion
2026-05-08 22:44:39 +02:00
parent d6ef23b431
commit 3e80ce2ea2
6 changed files with 194 additions and 72 deletions

View File

@@ -1,4 +1,14 @@
### v.2.5.0
## v.2.5.1
### Security
- Update JS dependencies with high CVE
### Fixes and improvements
- Fix form submissions losing values when field names contain spaces or non-word characters
## v.2.5.0
### Features
@@ -27,7 +37,7 @@
- Fix manager and presenter views while presentation conversion has no slide count yet
- Fix crash on event manager pages when an event has multiple activity leaders
### v.2.4.0
## v.2.4.0
### ⚠️ Breaking changes
@@ -59,7 +69,7 @@
- Fix italian translation (#179)
- Fix random poll choices (#184)
### v.2.3.2
## v.2.3.2
### Fixes and improvements
@@ -71,7 +81,7 @@
- Fix event code length validation (min: 5, max: 10)
- Fix presentation upload progress when editing an event
### v.2.3.1
## v.2.3.1
### Fixes and improvements
@@ -82,7 +92,7 @@
- Add option to force login to submit quizzes
- Fix url with question mark being flagged as a question
### v.2.3.0
## v.2.3.0
### Features

View File

@@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
"name": "assets",
"dependencies": {
"@sjmc11/tourguidejs": "^0.0.16",
"air-datepicker": "^3.5.0",
@@ -1093,9 +1094,10 @@
"license": "ISC"
},
"node_modules/immutable": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw=="
"version": "4.3.8",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz",
"integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==",
"license": "MIT"
},
"node_modules/is-binary-path": {
"version": "2.1.0",
@@ -1405,9 +1407,9 @@
}
},
"node_modules/minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1417,22 +1419,6 @@
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"dev": true,
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@@ -1499,9 +1485,10 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
@@ -1510,9 +1497,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"dev": true,
"funding": [
{
@@ -1615,17 +1602,16 @@
}
},
"node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"version": "7.5.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
@@ -2245,9 +2231,9 @@
"dev": true
},
"immutable": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw=="
"version": "4.3.8",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz",
"integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw=="
},
"is-binary-path": {
"version": "2.1.0",
@@ -2386,20 +2372,14 @@
"dev": true
},
"minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dev": true,
"requires": {
"minipass": "^7.1.2"
}
},
"mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"dev": true
},
"moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@@ -2459,14 +2439,14 @@
"dev": true
},
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="
},
"postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"dev": true,
"requires": {
"nanoid": "^3.3.11",
@@ -2533,16 +2513,15 @@
"dev": true
},
"tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"version": "7.5.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
"integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
"dev": true,
"requires": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
}
},

View File

@@ -61,7 +61,7 @@ defmodule ClaperWeb.EventLive.FormComponent do
form={f}
labelClass="text-white"
fieldClass="bg-gray-700 text-white"
key={safe_field_atom(field.name)}
key={field_key(field.name)}
name={field.name}
required={field.required}
value={
@@ -75,7 +75,7 @@ defmodule ClaperWeb.EventLive.FormComponent do
form={f}
labelClass="text-white"
fieldClass="bg-gray-700 text-white"
key={safe_field_atom(field.name)}
key={field_key(field.name)}
name={field.name}
required={field.required}
value={
@@ -176,13 +176,12 @@ defmodule ClaperWeb.EventLive.FormComponent do
end
end
defp safe_field_atom(name) when is_binary(name) do
String.to_existing_atom(name)
rescue
ArgumentError ->
if Regex.match?(~r/^[\w]{1,100}$/, name),
do: String.to_atom(name),
else: :invalid_field
defp field_key(name) when is_binary(name) do
try do
String.to_existing_atom(name)
rescue
ArgumentError -> String.to_atom(name)
end
end
def toggle_form(js \\ %JS{}) do

View File

@@ -1,7 +1,7 @@
defmodule Claper.MixProject do
use Mix.Project
@version "2.5.0"
@version "2.5.1"
def project do
[

View File

@@ -0,0 +1,62 @@
defmodule ClaperWeb.StatControllerTest do
use ClaperWeb.ConnCase, async: true
import Claper.{AccountsFixtures, FormsFixtures, PresentationsFixtures}
describe "POST /export/forms/:form_id" do
setup %{conn: conn} do
owner = confirmed_user_fixture()
presentation_file = presentation_file_fixture(%{user: owner}, [:event])
form =
form_fixture(%{
presentation_file_id: presentation_file.id,
title: "My Form",
fields: [
%{name: "First Name", type: "text", required: false},
%{name: "Email Address", type: "email", required: false}
]
})
%{conn: log_in_user(conn, owner), form: form, event: presentation_file.event}
end
test "exports CSV with field names containing spaces as headers and values", %{
conn: conn,
form: form,
event: event
} do
{:ok, _submit} =
Claper.Forms.create_or_update_form_submit(event.uuid, %{
"attendee_identifier" => "attendee-1",
"form_id" => form.id,
"response" => %{"First Name" => "Ada", "Email Address" => "ada@example.com"}
})
conn = post(conn, ~p"/export/forms/#{form.id}")
assert response_content_type(conn, :csv)
body = response(conn, 200)
[header_line, data_line | _] = String.split(body, "\r\n", trim: true)
assert header_line =~ "First Name"
assert header_line =~ "Email Address"
# The CSV row must contain the values, proving header→response key
# alignment works for space-containing field names. Regression: when
# submissions were saved under a wrong key, the CSV row was empty.
assert data_line =~ "Ada"
assert data_line =~ "ada@example.com"
end
test "returns 403 for users who don't own the event", %{form: form} do
stranger = confirmed_user_fixture()
conn = build_conn() |> log_in_user(stranger)
conn = post(conn, ~p"/export/forms/#{form.id}")
assert response(conn, 403) == "Forbidden"
end
end
end

View File

@@ -0,0 +1,72 @@
defmodule ClaperWeb.EventLive.FormComponentTest do
use ClaperWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Claper.{AccountsFixtures, FormsFixtures, PresentationsFixtures}
alias ClaperWeb.EventLive.FormComponent
defp setup_form(field_attrs) do
presentation_file = presentation_file_fixture(%{}, [:event])
form =
form_fixture(%{
presentation_file_id: presentation_file.id,
fields: field_attrs
})
%{event: presentation_file.event, form: form}
end
describe "rendering fields with non-alphanumeric names" do
test "renders an input whose name uses the original field name (with spaces)" do
%{event: event, form: form} =
setup_form([
%{name: "First Name", type: "text", required: false},
%{name: "Email Address", type: "email", required: false}
])
html =
render_component(FormComponent,
id: "form-component",
form: form,
current_user: user_fixture(),
attendee_identifier: nil,
event: event,
current_form_submit: nil
)
# The HTML name attribute must contain the actual field name —
# regression: a previous version mapped any non-\w name to :invalid_field,
# so all space-containing fields collided on `form_submit[invalid_field]`
# and submissions were saved under the wrong key.
assert html =~ ~s(name="form_submit[First Name]")
assert html =~ ~s(name="form_submit[Email Address]")
refute html =~ "invalid_field"
end
test "preserves submitted values when re-rendering for fields with spaces" do
%{event: event, form: form} =
setup_form([
%{name: "First Name", type: "text", required: false}
])
form_submit = %Claper.Forms.FormSubmit{
response: %{"First Name" => "Ada"}
}
html =
render_component(FormComponent,
id: "form-component",
form: form,
current_user: user_fixture(),
attendee_identifier: nil,
event: event,
current_form_submit: form_submit
)
assert html =~ ~s(value="Ada")
end
end
end