Public API availability check compares weekday string against integer days, rejecting all bookings
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 12
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
Environment:
- RoomVox 1.4.0
- PHP 8.2+ (any supported version)
Summary
PublicApiController compares the weekday as a string ("mon") against availabilityRules.rules[].days, which is stored as an array of integers (0–6, 0 = Sunday). The comparison can never match on PHP 8, so for any room with availability rules enabled the Public API treats every moment as outside the allowed hours.
The CalDAV/iTIP path is not affected — it does this correctly — so bookings made through a normal calendar client behave as expected. This is why the bug is easy to miss.
Steps to reproduce
- Create a room and enable Restrict booking hours, e.g. the built-in preset Weekdays 08–18 (stores
days: [1,2,3,4,5]) - Create an API token with scope
book - On a Tuesday at 10:00,
POST /api/v1/rooms/{id}/bookingswith a validtitle,startandendwell inside the window - Also call
GET /api/v1/rooms/{id}/status
Expected: booking is created (201); status reports free/busy.
Actual: booking is rejected with 422 {"error":"Booking is outside available hours"}; status always reports unavailable.
Rooms without availability rules are unaffected, since the whole block is skipped.
Cause
The data model stores weekdays as integers. From src/views/RoomEditor.vue:513-521:
const weekDays = computed(() => [
{ value: 1, label: t('roomvox', 'Mon') },
...
{ value: 0, label: t('roomvox', 'Sun') },
])
and the presets at src/views/RoomEditor.vue:221 pass applyPreset([1,2,3,4,5], '08:00', '18:00').
SchedulingPlugin::bookingFitsRule() reads this correctly — lib/Dav/SchedulingPlugin.php:817:
// Get day of week (0=Sunday, 6=Saturday) matching our data model
$startDay = (int)$start->format('w');
...
return in_array($startDay, $allowedDays, true)
PublicApiController instead formats the day as a lowercase abbreviation and compares it loosely against those integers, in three places:
1. roomStatus() — lib/Controller/PublicApiController.php:76
$dayOfWeek = strtolower($now->format('D')); // mon, tue, etc.
...
if (in_array($dayOfWeek, $rule['days'] ?? []) && ...
2. roomAvailability() — lib/Controller/PublicApiController.php:190
$dayOfWeek = strtolower($rangeStart->format('D'));
foreach ($room['availabilityRules']['rules'] ?? [] as $rule) {
if (in_array($dayOfWeek, $rule['days'] ?? [])) {
3. createBooking() — lib/Controller/PublicApiController.php:397
$dayOfWeek = strtolower($startDt->format('D'));
...
if (in_array($dayOfWeek, $rule['days'] ?? []) && ...
return new JSONResponse(['error' => 'Booking is outside available hours'], 422);
Since PHP 8 changed string↔int comparison, "mon" == 1 is false (under PHP 7 semantics "mon" == 0 would have been true, which would have made Sunday spuriously match). Confirmed on PHP 8.5.4:
date: 2026-09-07 (Monday), rule days: [1,2,3,4,5]
PublicApiController: in_array("mon", [1,2,3,4,5]) = false
SchedulingPlugin: in_array(1, [1,2,3,4,5], true) = true
Impact
For rooms with availability rules enabled:
| Endpoint | Effect |
|---|---|
POST /api/v1/rooms/{id}/bookings |
Always 422 — booking via the API is impossible |
GET /api/v1/rooms/{id}/status |
Always reports unavailable |
GET /api/v1/rooms/{id}/availability |
Falls through to the 00:00–23:59 default, so the room's real opening hours are never reflected |
This makes the Public API unusable for its documented purpose (displays, kiosks, digital signage) on exactly those rooms that have opening hours configured.
Suggested fix
Use (int)$dt->format('w') and strict in_array(..., true) in all three places, matching SchedulingPlugin. Given the logic is now duplicated in four locations with two different data interpretations, extracting a single shared helper (e.g. on RoomService or a small AvailabilityRules value object) and having both SchedulingPlugin and PublicApiController call it would prevent this class of drift.
Worth adding a regression test that feeds the same room + timestamp through both paths and asserts they agree.
Related observations
While verifying the above, two adjacent inconsistencies surfaced. Happy to split these into separate issues if preferred:
-
Availability rules are not applied per occurrence.
docs/features/availability-rules.md:51states "A weekly meeting fails if any week falls outside the availability rules", butisWithinAvailability()is called once, on the master event'sDTSTART/DTEND(lib/Dav/SchedulingPlugin.php:208). The booking-horizon check does handle recurrence (:720-781), andhasConflict()expands occurrences (lib/Service/CalDAVService.php:1000-1021) — only the availability rules do not. Either the docs or the code should be corrected. -
BookingApiControllerenforces no availability rules or horizon at all — it callshasConflict()only (:100,:194,:233). Bookings created through the admin UI therefore bypass configured opening hours. This may well be intentional (admins may override), but it is not documented anywhere.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in lib/Controller/PublicApiController.php at roomStatus(), roomAvailability(), and createBooking(), then compare their weekday handling with SchedulingPlugin::bookingFitsRule() in lib/Dav/SchedulingPlugin.php:817. Verify the three Public API endpoints using the same integer weekday interpretation and confirm that valid in-hours bookings succeed while out-of-hours requests remain rejected. Add a regression test if the project’s existing test structure supports it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100