Continued fixes from the multi-agent review:
- recurring tickets cron: now that the parse error is fixed the job runs,
exposing two latent bugs. (1) next_run_at was only advanced after the
full success path, so any failure (e.g. a NULL created_by passed to the
non-nullable assignTicket() $assignedBy -> TypeError) left it in the past
and re-created a duplicate ticket every cron cycle. Added an atomic
claimForRun() (conditional UPDATE gated on still-due) called BEFORE
creation, which also prevents overlapping runs from double-creating.
(2) The cron used a raw mysqli with no utf8mb4, corrupting non-ASCII
content; it now uses Database::getConnection(). Also guard the assignment
so created_by NULL falls back to the assignee.
- bulk delete: attachment files were unlinked inside the DB transaction, so
an atomic-mode rollback restored rows but the files were already gone.
deleteTicket() can now defer file removal to the caller, and
BulkOperationsModel deletes files only after a successful commit.
- UserModel: back-tick the `groups` column (reserved word on MySQL 8.0.2+).
- create_ticket_api.php: stop leaking raw DB/exception messages to callers;
log server-side and return a generic error. (Also includes a pre-existing
working-tree tweak that adds title to the manual-ticket dedupe hash.)
- CI: semgrep install failed under PEP 668; add --break-system-packages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fix fatal PHP error in UserModel::getAllGroups() - typo 'setCache'
should be 'setCached', was causing ticket page to fail to render
- Fix settings.js null reference errors when timezone element missing
on ticket page (only exists on dashboard)
- Fix ESC key detection for settings modal (checked 'block' but modal
uses 'flex' display)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Consolidate StatsModel queries from 12 to 3 using conditional aggregation
- Add input validation to DashboardController (sort columns, dates, priorities)
- Combine getCategories/getTypes into single query
- Add transaction support to BulkOperationsModel with atomic mode option
- Add depth limit (20) to dependency cycle detection to prevent DoS
- Add caching to UserModel.getAllGroups() with 5-minute TTL
- Improve ticket ID generation with 50 attempts, exponential backoff, and fallback
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cache optimization with automatic expiration:
1. New Cache Structure:
- Changed from simple array to TTL-aware structure
- Each entry: ['data' => ..., 'expires' => timestamp]
- 5-minute (300s) TTL prevents indefinite stale data
2. Helper Methods:
- getCached($key): Returns data if not expired, null otherwise
- setCached($key, $data): Stores with expiration timestamp
- invalidateCache($userId, $username): Manual cache clearing
3. Updated All Cache Access Points:
- syncUserFromAuthelia() - User sync from Authelia
- getSystemUser() - System user for daemon operations
- getUserById() - User lookup by ID
- getUserByUsername() - User lookup by username
Benefits:
- Prevents memory leaks from unlimited cache growth
- Ensures user data refreshes periodically
- Maintains performance benefits of caching
- Automatic cleanup of expired entries
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>