Skip to content

fix: clearing one custom field must not delete the others - #862

Merged
blaipr merged 1 commit into
mainfrom
fix/clearing-one-custom-field-must-not-delete-the-others
Aug 23, 2026
Merged

fix: clearing one custom field must not delete the others#862
blaipr merged 1 commit into
mainfrom
fix/clearing-one-custom-field-must-not-delete-the-others

Conversation

@blaipr

@blaipr blaipr commented Aug 23, 2026

Copy link
Copy Markdown
Member

The save path deleted a custom field's row when its posted value was empty, which is
right — saving the blank instead would leave the old ciphertext behind. But the delete
it made was delete([$itemId], $moduleId), and that is keyed on the item and the module
with no definition at all: it removes every custom field value on the item.

So emptying one field on any item that has more than one wiped the rest of them in the
same save, including encrypted values the person saving could not see and had not
touched. Nothing reported it. The save succeeded and the other fields simply came back
blank, which looks like the fields were never filled in rather than like data loss.

deleteBatch() is keyed that way because it exists for the other job — deleting the item
takes its fields with it — and the single-field case had been given the same method.
This adds deleteForDefinition() through the repository, both ports and the service, and
leaves deleteBatch() to the case it was written for.

Also narrows updateCustomFieldsForItem()'s $itemId from int|array to int. Every
caller passes a single id, and the array form was never workable here: it is written
straight into the model's itemId, so an array would have been stored as one. The wider
type came from the batch delete, where it does make sense.

Checked by putting the old call back: the test that pins which row is deleted fails, and
so does the one asserting the fields either side of a cleared one survive.

The save path deleted a custom field's row when its posted value was empty, which is
right — saving the blank instead would leave the old ciphertext behind. But the delete
it made was `delete([$itemId], $moduleId)`, and that is keyed on the item and the module
with no definition at all: it removes *every* custom field value on the item.

So emptying one field on any item that has more than one wiped the rest of them in the
same save, including encrypted values the person saving could not see and had not
touched. Nothing reported it. The save succeeded and the other fields simply came back
blank, which looks like the fields were never filled in rather than like data loss.

deleteBatch() is keyed that way because it exists for the other job — deleting the item
takes its fields with it — and the single-field case had been given the same method.
This adds deleteForDefinition() through the repository, both ports and the service, and
leaves deleteBatch() to the case it was written for.

Also narrows updateCustomFieldsForItem()'s $itemId from `int|array` to `int`. Every
caller passes a single id, and the array form was never workable here: it is written
straight into the model's itemId, so an array would have been stored as one. The wider
type came from the batch delete, where it does make sense.

Checked by putting the old call back: the test that pins which row is deleted fails, and
so does the one asserting the fields either side of a cleared one survive.
@blaipr
blaipr merged commit 2a2b65c into main Aug 23, 2026
8 checks passed
@blaipr
blaipr deleted the fix/clearing-one-custom-field-must-not-delete-the-others branch August 23, 2026 22:38
Sign up for free to 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.

1 participant