Setting a PIN containing < or > silently cleared it: the length check ran before escapeSql(), whose strip_tags() then emptied the value, and the UPDATE stored the blank while flashing success. Length is now checked after sanitising.
Password and PIN changes require the current password. SSO contacts are exempt - no local password to check, and the IdP already did it.
New index on logs(log_user_id, log_client_id) for the portal profile and activity pages, which were scanning the whole table twice per profile view. admin/audit_logs.php's date filter rewritten as a half-open range so KEY log_created_at is usable - DATE(log_created_at) BETWEEN made it non-sargable.
Portal statement page and PDF now render in the client's currency, matching the guest view and the emailed statement.
Quick Send asks for confirmation; confirm-link extended to submit buttons.
Portal audit entries logged an empty name - client/post.php used , which only exists agent-side.
Replaces the duplicated primary/billing/technical checks across portal pages, post.php handlers, nav, and dashboard with contactCan()/enforceContactCan(). Same behavior, but the rules now live in one place instead of being copy-pasted, which is what let them drift before. file.php keeps its 404 response; ticket-visibility and approval-routing checks are intentionally left as-is.
- Implemented file upload capabilities, allowing users to attach files to documents with support for various formats (PDF, Word, text).
- Created modals for adding new documents and uploading files, improving user experience.
- Added functions for formatting file sizes and retrieving appropriate icons based on file extensions.
- Updated the document display logic to handle attached files, providing inline previews for PDFs and images.