- ApiAccountController::lookup: reduce from 4 to 3 returns via ternary
- AttestationController::ventes: reduce from 5 to 2 returns by extracting
decodeAndVerifyHash() helper; add TPL_NOT_FOUND_VENTES constant for the
template literal duplicated 5 times
- AnalyticsCryptoService::decrypt: reduce from 4 to 2 returns by extracting
tryDecryptJsFormat() helper
- InfraService::fmtDuration: reduce from 4 to 1 return using match(true)
- InfraService: replace nested ternary with match(true) for SSL status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The constant was incorrectly defined as self::NO_SNAPSHOT_MSG = self::NO_SNAPSHOT_MSG
causing a PHP fatal error. Replace with the actual string value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New API endpoint secured by X-App-Secret header (no JWT auth required).
Accepts an email in the request body and returns the user's id and
stripeAccountId if present. Includes 6 unit tests covering all cases
(success, missing secret, invalid secret, missing email, user not found).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AnalyticsController::track: extract handleTrackData(), reduce from 7 to 3 returns
- ApiAuthController::ssoValidate: extract ssoError/ssoSuccess helpers, reduce from 6 to 3 returns
- ApiLiveController::scan: extract findTicketFromRequest(), reduce from 4 to 3 returns
- ApiLiveController::scanForce: flatten logic, reduce from 6 to 3 returns
- ApiLiveController::processScan: extract isAlwaysValidTicket, checkRefusal,
markScannedAndRespond, reduce cognitive complexity from 16 to under 15
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ArrayAdapter implements both CacheInterface and CacheItemPoolInterface,
matching the intersection type on the constructor parameter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @param array<string, mixed> to AnalyticsController::createVisitor()
- Add @param/@return array<string, mixed> to AnalyticsCryptoService encrypt/decrypt
- Add @param array<string, mixed>|null to InfraService calcCpuPercent/calcMemory
- Merge duplicate docblocks on InfraService::calcMemory()
- Use intersection type CacheInterface&CacheItemPoolInterface for MeilisearchService cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add DQL_EXCLUDE_INVITATIONS, DQL_BB_EXCLUDE_INVITATIONS, CONTENT_TYPE_PDF constants
- Reduce createAccreditation from 4 to 3 returns by combining validations
- Extract collectAttestationBillets, buildAttestationStats, buildAttestationTicketDetails
from eventAttestation to reduce cognitive complexity from 18 to under 15
- Remove unused $totalRevenue, duplicate $label, and securityKey from attestation details
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused $indexes variable (listIndexes() kept as health check)
- Make $fixMissing parameter required (always passed), remove && $fixMissing check
- Remove redundant array_values() on array_map result (already a list)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add integrity/crossorigin attributes to chart.js and html5-qrcode CDN scripts
- Replace md5() with hash('xxh128') for Meilisearch cache key generation (non-sensitive context)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Template accesses redis_global.error, postgres.error and pgbouncer.error
in the else branch when connected=false, but the key was not provided.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Template accesses server.hostname etc. but server was passed as empty array
when infra.json does not exist. Now provides all expected keys with '?' defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The script loading logic was removed from initCookieConsent() but tests
still expected it. Removed 5 obsolete tests, kept 7 core consent tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reduce requireStripeReady() from 4 returns to 2 by combining conditions
- Extract SCANNER_PATH constant in ScannerController to avoid duplicated "/scanner/" literal
- Remove user-scalable=no from scanner viewport meta (WCAG accessibility)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract filterUsers() and createUserIndex() from checkUserIndex() to reduce cognitive complexity from 16 to under 15
- Add INDEXES_ENDPOINT constant to replace duplicated "/indexes" literal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace unpkg.com with cdn.jsdelivr.net in sw.js cache list
- Fix sw.js scope to /scanner/
- Remove unused $indexes parameter from checkAllIndexes()
- Extract duplicated " [%s] Index missing" literal to INDEX_MISSING_MSG constant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create Attestation entity with reference, signature hash (HMAC-SHA256), event, user, payload
- Add migration Version20260326180000 for attestation table
- Save each attestation in DB with unique signature for tamper-proof verification
- Add public route /attestation/ventes/r/{reference} for QR code verification (short URL)
- Keep fallback /attestation/ventes/{hash} route for base64-signed verification
- Public page shows "Attestation conforme" with signature proof, no detailed data
- QR code on PDF now uses short reference URL instead of full base64 hash (scannable)
- Increase QR code resolution to 300px for better readability
- Display verification URL on PDF next to QR code
Attestation PDF improvements:
- Rename "ATTESTATION DE VENTES" to "ATTESTATION"
- Add two modes: "Attestation detaillee" (with ticket list) and "Attestation simple" (certification only)
- Simple mode: certifies figures are valid, only paid billets/votes confirmed by Stripe count
- Detailed mode: adds full ticket listing with reference, order number, billet name, buyer name
- No amounts displayed in either mode
- Gold color scheme (#fabf04) for headers, borders, table headers, summary box
- Larger text in QR verification box for readability
Scanner: ROLE_ROOT buyer tickets always validate at scan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SSO login button to scanner PWA with Keycloak redirect flow via session state
- Add manual scan mode via security key (16 chars) alongside QR camera scan
- Add audio feedback: good (accepted), warning (already scanned), refused sounds
- Add unique scan counter per reference (no double counting same ticket)
- Add order details display in scan results (order number, email, total, items)
- Add force validation button for refused tickets (organizer/ROLE_ROOT only), sends email notification
- Add already_scanned warning only for same-day scans, exit_definitive only same day
- Staff and exposant tickets always validate regardless of state
API: ROLE_ROOT access to all events, categories, billets, and scan endpoints
- ROLE_ROOT bypasses ownership checks on all /api/live/* endpoints
- ROLE_ROOT can login via API (email/password and SSO)
- Scan API accepts securityKey parameter in addition to reference
- Scan response includes billetType, buyerEmail, and full order details with items
Event management: tickets tab, staff/exposant accreditations, attestation PDF
- Add Tickets tab listing all sold tickets with search, download PDF, resend email, cancel actions
- Add Staff/Exposant accreditation form in Invitations tab, generates dedicated non-buyable billet
- Add Attestation tab to generate sales certificate PDF with category/billet selection
- PDF billet template shows STAFF/EXPOSANT badge with distinct colors (black/purple)
- Exclude invitations from all financial stats (event stats, admin dashboard, organizer finances)
- Fix sold counts to exclude invitations in categories recap
- Use actual Stripe fee parameters instead of hardcoded values in commission calculations
- Add commission detail breakdown (E-Ticket + Stripe) in categories and stats tabs
Admin: download tickets for orders
- Add download button on admin orders page (single PDF or ZIP for multiple tickets)
Scanner PWA fixes: CSP (unpkg -> jsdelivr), service worker scope (/scanner/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When SECRET_ANALYTICS changes (deploy), old uid/hash become invalid.
On 403, clear session and auto-retry with a new visitor creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mentions legales, CGU, CGV, hebergement, cookies, RGPD, conformite
are now allowed for crawling instead of disallowed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Labels and card text: text-gray-400 -> text-gray-600 on #fbfbfb bg
- Empty state message: text-gray-400 -> text-gray-600 on white bg
- Add explicit width/height to navbar logo to prevent CLS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- text-yellow-500 on white bg had ratio ~1.9 (need 4.5), now text-yellow-700
- text-indigo-600 links on white bg had ratio ~3.8, now text-indigo-800
with permanent underline for link visibility (WCAG 1.4.1)
- Cookie banner link also updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents stale Doctrine L2 cache and app cache from causing issues
after schema changes. Clears both filesystem cache and Redis pool.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New SECRET_ANALYTICS variable replaces kernel.secret for analytics
- Ansible generates a random 32-char secret at each deploy
- Endpoint token and encryption key change with every deployment
- Existing sessions will get new visitor_id after deploy (expected)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The endpoint path is now /t/<8-char hash of APP_SECRET> instead of
static /t. Token is injected via data-e attribute on body, read by JS.
Server validates token on every hit, returns 404 if invalid.
Changes with each APP_SECRET = impossible to hardcode in a blocker.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SUBSTRING does not work on timestamp in PostgreSQL. Use native SQL
with CAST(created_at AS DATE) for daily chart aggregation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bar chart: visitors per day
- Line chart: pageviews per day (with fill)
- Bounce rate KPI with color coding (green/yellow/red)
- Filter out self-referrers (ticket.e-cosplay.fr, esyweb.local)
- Uses Chart.js via cdn.jsdelivr.net (already in CSP)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bounce rate = visitors with only 1 pageview / total visitors.
Color-coded: green <40%, yellow <60%, red >=60%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Send confirmation email when no data found for access or deletion request
- Add DPO contact (DPO-167945, E-Cosplay) to both access and deletion PDFs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
symfony/uid not installed, replace Uuid::v4() with random_int based
UUID v4 generation (RFC 4122 compliant).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web Crypto API AES-GCM outputs: iv + ciphertext + tag (tag appended)
PHP openssl was using: iv + tag + ciphertext (tag in middle)
Now both use the same format: iv (12 bytes) + ciphertext + tag (16 bytes).
Decrypt tries JS format first, falls back to PHP format for compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>