Fix empty-string date fields silently becoming "now" + document property-level RBAC#1294
Conversation
…ix-empty-string-date-conversion
…ix-empty-string-date-conversion
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 147/147 | |||
| npm | ✅ | ✅ 599/599 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-21 09:12 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 147/147 | |||
| npm | ✅ | ✅ 599/599 | |||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-21 09:19 UTC
Download the full PDF report from the workflow artifacts.
|
Nice fix on the datetime normalization — 1. unset($countQuery['_limit'], $countQuery['_offset'], $countQuery['_page'],
$countQuery['_facetable'], $paginatedQuery['_extend']);
// ^^^^^^^^^^^^^^^ should be $countQueryThe line above correctly unsets 2. Neither is mentioned in the description, CHANGELOG, or OpenSpec artifacts. If they're intentional, happy to be corrected — otherwise recommend dropping both hunks to keep this PR scoped to the datetime fix + RBAC docs. |
WilcoLouwerse
left a comment
There was a problem hiding this comment.
See previous comment
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 147/147 | |||
| npm | ✅ | ✅ 599/599 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-21 11:33 UTC
Download the full PDF report from the workflow artifacts.
|
Thanks for the quick turnaround — both issues are resolved. LGTM on the datetime fix. Small nit (non-blocking): the restored Approving. 👍 |
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 147/147 | |||
| npm | ✅ | ✅ 599/599 | |||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-21 11:37 UTC
Download the full PDF report from the workflow artifacts.
Summary
Two related changes, both driven by work on issue #1292:
date/date-timefields no longer silently round-trip as the current date/time. On every user-supplied datetime path (object write, read/render, bulk import, metadata, search,ObjectService::normalizeDateValues) the raw string is now routed through a new centralDateTimeNormalizer.null,"", and whitespace-only values normalise tonullinstead of hitting PHP'snew DateTime('')footgun (which returns "now").$organisation,$userId,$now,$lte, etc.) is now documented as a first-class feature. Adds a new feature page, updates the existingaccess-control.md, and removes "Field-Level Authorization" and "Time-Based Permissions" from the Future Enhancements list since both are implemented.Closes #1292.
The bug in one sentence
(new DateTime(''))->format('Y-m-d')returns today's date string rather than failing or returningnull. Multiple code paths fed user-supplied datetime strings into this pattern unguarded, so a form that cleared a date field and POSTed"publishedAt": \"\"silently populated the object with the current timestamp — the exact opposite of what the user intended, and indistinguishable on read from a legitimate value.What changed
New:
DateTimeNormalizerservicelib/Service/DateTimeNormalizer.php— single canonical entry point for user-datetime conversion. All new code MUST go through this class (documented in the class docblock and referenced in the OpenSpec change).Contract:
normalize(mixed $value): ?DateTimeImmutableDateTimeImmutablefor valid input;nullfornull/ empty / whitespace / unparseable / wrong type. Parse failures log at debug level.formatForDatabase(mixed $value): ?stringY-m-d H:i:sornull.formatForIso8601(mixed $value): ?stringnull.Injected via constructor (no service-locator /
\OC::\$serverlookups).Full unit test coverage in
tests/Unit/Service/DateTimeNormalizerTest.php: null, empty, whitespace (" ","\t","\n"), ISO 8601 with offset, Zulu, DB format, date-only, passthrough of existingDateTime/DateTimeImmutable, garbled strings, numeric/array/object input.Migrated call sites
All five primary sites where user-supplied datetime strings used to hit
new DateTime(\$value)directly:lib/Service/ObjectService.php::normalizeDateValuesDateTimeNormalizer; trim → null short-circuit; accepts already-validY-m-ddates as-islib/Db/MagicMapper/MagicStatisticsHandler.php(dateanddate-timebranches)Y-m-dor ISO 8601lib/Db/MagicMapper/MagicBulkHandler.php::formatDateTimeForDatabaseformatForDatabaselib/Db/MagicMapper.phpmetadata handlingcreated/updated/expiresmetadatacreated/updatedlib/Db/ObjectHandlers/MariaDbSearchHandler.php::normalizeDateValuenullon empty/invalid input (skips the predicate) rather than matching "today"Remaining
new DateTime(\$var)call sites were audited and classified as either internal/trusted values (lock expiry, cached schema/org metadata, already-validated DB columns) or out-of-scope controller params that 400/404 on garbage rather than silently coercing. Full inventory inopenspec/changes/fix-empty-string-date-conversion/tasks.md§5.4.Tests
tests/Unit/Service/DateTimeNormalizerTest.php— full unit coverage of the new service.tests/Unit/Db/MagicStatisticsHandlerTest.php— regression test: stored empty-string date/date-time renders asnull, not today/now."publishedAt": \"\"now persistsNULLin the DB; prior to the fix it persisted the current datetime.Documentation
docs/Features/property-authorization.md— concept, schema structure, rule shape (group+ optionalmatch), full operator catalogue ($eq,$ne,$gt,$gte,$lt,$lte,$in,$nin,$exists), dynamic variables ($organisation/$activeOrganisation,$userId/$user,$now), worked examples (scheduled publication viapublishedAt: { \"\$lte\": \"\$now\" }, organisation-scoped internal notes, role-restricted updates), evaluation semantics (reads strip the field, writes reject with validation error, admin bypass, null handling), and performance notes (hasPropertyAuthorization()short-circuit).docs/Features/access-control.md— added cross-link in the Permission Levels bullet; new "Property-Level Authorization" section with canonical examples pointing readers at the full doc; removed "Field-Level Authorization" and "Time-Based Permissions" from Future Enhancements (both now shipped).CHANGELOG.md— user-facing Unreleased entry describing the empty-string →nullbehaviour correction across write / read / bulk / metadata / search.OpenSpec artifacts
Change tracked at
openspec/changes/fix-empty-string-date-conversion/:proposal.md— problem statement and scopedesign.md— call-site inventory, decisions (D1–D5), seed data, migration plantasks.md— 30+ tasks, ~24 complete (implementation + unit tests + docs); integration tests and cross-app verification tracked as remainingspecs/datetime-input-handling/spec.md— new capability specTest plan
DateTimeNormalizerTest,MagicStatisticsHandlerTest){ \"publishedAt\": \"\" }→ stored asNULL, rendered asnull{ \"publishedAt\": \"2026-04-20T14:00:00Z\" }→ round-trips correctlypublishedAt→ staysnull(unchanged behaviour)opencatalogi/softwarecatalogtest suites against the patched backend (tasks 7.1–7.3)composer check:strictgreen on CI (PHPCS / PHPMD / Psalm / PHPStan)Migration notes
Objects already persisted with an empty-string date will continue to render as today until they are next written. A one-shot
UPDATE ... SET col = NULL WHERE col = ''maintenance command is flagged as optional follow-up (openspec/changes/fix-empty-string-date-conversion/design.md§Migration Plan); on next read/save the normaliser will correct the value in place.Backwards compatibility
No breaking changes to the API contract. Previously, clients that sent
\"\"for a date field received back the current datetime on the next read — this was never documented behaviour and is the bug being fixed. Clients that correctly sendnull(or omit the key) see no change. Clients that correctly send a valid ISO 8601 value see no change.