Problem/Motivation

Any site where bat_event entity IDs coincide with wrong state entity IDs.

`FullCalendarFixedStateEventFormatter::eventLabel()` contained a Step 3 block that called
`bat_event_load($event->getValue())`, treating the event's value (a **state entity ID**
when read from the `BAT_STATE` store) as a **bat_event content entity ID**. State entities
and bat_event entities are separate content entity types with independent auto-increment ID
sequences. When IDs from the two sequences happen to match, `bat_event_load()` returns a
completely unrelated bat_event, and the `state_info` it carries silently overrides the
correct state that was loaded in Step 1. This causes the calendar to render events with the
wrong colour and label.

Steps to reproduce

1. Install BAT + BEE on a Drupal site with at least one daily-bookable content type.
2. Create a node and write several availability events (available / not-available) so that
both `bee_daily_available` (state ID 1) and `bee_daily_not_available` (state ID 2)
bat_event entities exist in the database.
3. Ensure at least one bat_event entity has an entity ID that matches the ID of a *different*
state (e.g., bat_event entity #2 exists and its state is `bee_daily_available`, while
state entity #2 is `bee_daily_not_available`).
4. View the node's availability calendar.

**Expected:** Each date range displays with the colour and label of its actual recorded state.

**Actual:** Some date ranges display with the colour and label of a different state — the one
carried by whichever bat_event entity happened to share an ID with the state entity ID.

Cause

`eventLabel()` operated in three steps:

```php
// Step 1 — load state entity by the event's value (state ID from BAT_STATE store):
if ($data['state_info'] = bat_event_load_state($data['event']->getValue())) {
$data['calendar_label'] = $data['state_info']->getCalendarLabel();
}

// Step 2 — use unit name as label if available:
if ($data['bat_unit'] && $data['bat_unit']->getName()) {
$data['calendar_label'] = $data['bat_unit']->getName();
return $data;
}
// Step 3 — BUG: load a bat_event entity using the same value, then override state_info:
elseif ($data['event']->getValue()) {
$bat_event = bat_event_load($data['event']->getValue());
if ($bat_event) {
$data['state_info'] = $bat_event->get('event_state_reference')->entity;
$data['calendar_label'] = $data['state_info']->getCalendarLabel();
}
}
```

The `if (unit name)` condition in Step 2 is false for any unit whose name is empty or
whose calendar label should come from the state rather than the unit — which is the normal
BEE case. Execution therefore falls into the `elseif` of Step 3.

Step 3 calls `bat_event_load($event->getValue())`. `$event->getValue()` returns a
**state entity ID** (e.g., `1` for available, `2` for not-available). `bat_event_load()`
loads a **bat_event content entity** by that same integer. bat_event entity #1 and state
entity #1 are different objects; they share an integer ID only by coincidence of their
independent auto-increment sequences.

When a bat_event entity exists for that integer ID, `state_info` is silently overwritten
with whatever state *that* bat_event carries, discarding the correct value from Step 1.
The calendar then renders the wrong colour and label for those dates.

- Any calendar event whose state entity ID coincides with a bat_event entity of a *different*
state will display with the wrong colour and label.
- The bug is non-deterministic: it manifests or not depending on which bat_event entities
happen to exist at query time, making it difficult to reproduce consistently.
- On sites with few calendar events, IDs 1 and 2 are common to both sequences, so
available/not-available labels may swap silently.

Proposed resolution

Remove Step 3 entirely. Step 1 already performs the authoritative state lookup
(`bat_event_load_state()` calls `State::load($value)`, which is exactly correct when the
store is `BAT_STATE`). No second lookup is needed or safe.

Replace the broken `if (unit name) { return } elseif (getValue()) { bat_event_load... }`
block with a simple label fallback:

**Before:**
```php
if ($data['bat_unit'] && $data['bat_unit']->getName()) {
$data['calendar_label'] = $data['bat_unit']->getName();
return $data;
}
elseif ($data['event']->getValue()) {
$bat_event = bat_event_load($data['event']->getValue());
if ($bat_event) {
$data['state_info'] = $bat_event->get('event_state_reference')->entity;
$data['calendar_label'] = $data['state_info']->getCalendarLabel();
}
}
```

**After:**
```php
if (!$data['calendar_label']) {
$data['calendar_label'] = isset($data['state_info'])
? $data['state_info']->getCalendarLabel()
: '---';
}
```

The state entity loaded in Step 1 is now never overridden. The calendar label is set from
the correct `state_info`, with a safe `'---'` fallback if no state entity matched.

## Relationship to EventsRestResource BAT_EVENT/BAT_STATE mismatch

A separate, deeper bug in `EventsRestResource` (see companion issue report) causes the REST
API to populate event values with **bat_event entity IDs** instead of state entity IDs, by
constructing the calendar store with `BAT_EVENT` instead of `BAT_STATE`. When that bug is
also present, `eventLabel()` Step 1 (`bat_event_load_state(bat_event_id)`) returns null for
any bat_event entity ID greater than the highest state entity ID (typically 6), and all such
events render as red / "N/A" regardless of Step 3.

The formatter fix (removing Step 3) is correct and necessary independently of the
`EventsRestResource` fix:

- With `BAT_STATE` store (correct): Step 1 reliably loads the right state; Step 3 was an
unreliable override that could corrupt results.
- With `BAT_EVENT` store (the bug): Step 3 also produces wrong results because bat_event
entity IDs, not state IDs, are passed to `bat_event_load()`.

Both fixes should be applied together.

Remaining tasks

I have tested this on my own instance running under DDEV, additional verification and review would be welcome.

User interface changes

None

API changes

None

Data model changes

None

Sponsorship

None

Issue fork bat-3576448

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

owens-d created an issue. See original summary.

nickolaj made their first commit to this issue’s fork.

nickolaj’s picture

Status: Active » Needs review

Removed the faulty Step 3 in `FullCalendarFixedStateEventFormatter::eventLabel()` that called `bat_event_load()` with a state entity ID, corrupting calendar colours when IDs coincided between state and bat_event entities. Replaced with a simple fallback for the calendar label.

  • afagioli committed 548a3684 on 11.1.x
    Issue #3576448: Remove faulty bat_event_load() call in...
afagioli’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.