Track all logins (not just first) via ApplicationLogger user_login action.
Add User Login filter option to logs-viewer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only admin_emails users were saved to user_roles.json.
Now all users are recorded with default role on first login so they
appear in the admin panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
firebase/php-jwt v6 requires 'alg' in each JWK, but Azure AD JWKS
endpoint omits it. Inject RS256 for any key missing the parameter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change redirect_uri to app root (without /auth.php) to match what's
registered in Azure portal. Use relative URLs for auth fetch and reload
on success instead of computed absolute paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cannot use ternary operator directly in HEREDOC string.
Moved conditional logic outside HEREDOC into variable first.
This fixes the parse error on line 203.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Shows step-by-step loading of all dependencies with errors displayed.
Use on server to see exact error causing 500.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created server-setup.sh to fix server environment:
- Creates logs/ directory with proper permissions
- Sets file permissions (755 for dirs, 644 for config, 600 for JWT)
- Tests ApplicationLogger functionality
- Provides instructions for installing PHP zip extension
Issues Found on Server:
1. logs/ directory missing (causing ApplicationLogger to fail)
2. PHP zip extension missing (needed for download-all-csv.php)
3. Vendor directory not writable (minor issue)
Run on server:
chmod +x server-setup.sh
./server-setup.sh
sudo apt-get install php-zip
sudo systemctl restart apache2
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created server-check.php to diagnose server issues:
- PHP version check (requires 7.4+)
- Extension checks (curl, json, mbstring, openssl, zip)
- File permission checks (logs/, vendor/)
- Composer dependency verification
- Configuration file existence
- PHP settings (upload limits, memory)
- Service class loading tests
Use this to troubleshoot 500 errors on production server.
Access: /server-check.php
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CSV Transformation Fixes:
- Title now includes language code: "{OriginalTitle}_{ISO}" (e.g., "Syndication_en-GB")
- Added "Creative Execution" column with original global title
- Both columns properly populated for all 16 regional CSVs
Email Template System:
- Created EmailTemplates.php with professional HTML templates
- Based on Ferrero automation email styling
- Templates for all workflows:
* Asset Submission Success/Failed
* Global to Local Started/Complete/Failed
* Box Upload Success
- L'Oréal brand colors (Yellow #FFC407, Black #000000, Green for success)
- Responsive design with proper HTML structure
- Clean, professional layout with color-coded status boxes
Email Service Enhancements:
- Added sendTemplate() method for templated emails
- SMTP now supports HTML multipart emails (text + HTML)
- Mailgun API support for HTML
- Proper MIME boundaries and headers
- Extract subject from template HTML
Notification Updates:
- upload-to-box.php: Uses templates with full data (campaign, business unit, file count)
- submit.php: Logs all asset submissions
- All emails sent as professional HTML with fallback text
Template Features:
- Color-coded headers (green=success, red=error, yellow=warning)
- Info boxes with campaign details
- Data tables for multiple items
- Action required sections
- Footer with branding
All notifications now send beautiful, branded HTML emails to users!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Set enabled=true in omg_api configuration.
Updated API key should now have proper permissions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Documentation For:
Activity Logging & Reporting Section:
- Logging system overview and what gets logged
- Example JSON log entry structure
- Activity Logs Viewer features and use cases
- Statistics dashboard capabilities
- Export and filtering options
- Log file location and format
Email Notifications Section:
- SMTP configuration details
- Notification types (started, completed, failed)
- Recipient configuration (logged-in user)
- How to enable/disable
OMG API Section:
- Current status (configured but disabled)
- Permission issue explanation (403 error)
- Fallback business unit configuration
- Steps to enable when permissions granted
Project Structure:
- Complete file tree with all 3 tabs
- Organized by functionality (Auth, Services, Tab 1, Tab 2, Tab 3)
- Shows which files are in git vs excluded
- Clear separation of frontend/backend files
API Endpoints:
- Added logs-viewer.php endpoint documentation
- Query parameters for filtering
- Export functionality
- Updated upload-to-box.php to mention logging
Navigation:
- Added Activity Logs as Tab 3
- 3-tab system: Master Global Asset Submission, Global to Local, Activity Logs
The README now provides complete documentation for all features including the new logging and reporting system.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
ApplicationLogger Class:
- Structured JSON logging to logs/application.log
- Tracks all actions: master_asset_submission, global_to_local_transform, box_upload, omg_api_lookup
- Captures: timestamp, user email/name, status, detailed data, IP address, user agent
- Methods: getRecentLogs(), getLogsByAction(), getLogsByUser(), getStatistics()
Logging Integration:
- submit.php: Logs all asset submissions (success/failure)
- process-csv.php: Logs all CSV transformations
- upload-to-box.php: Logs all Box uploads
- Tracks campaign numbers, business units, file counts, dates
Logs Viewer (logs-viewer.php):
- New tab "Activity Logs" in navigation
- Statistics dashboard (total actions, success rate, errors, unique users)
- Filterable table (by action type, user, date range)
- View detailed data for each log entry (expandable JSON)
- Export to CSV functionality
- Shows last 100 entries by default (configurable)
Email Service Enhancement:
- Added SMTP support (in addition to Mailgun API)
- Configured for smtp.mailgun.org with provided credentials
- Sends notifications to logged-in user email
- Proper SMTP protocol implementation with AUTH LOGIN
OMG API Configuration:
- Added 'enabled' flag (currently false due to 403 error)
- Added 'fallback_business_unit' for when OMG disabled
- Uses X-API-Key header format
- Comprehensive error logging
- When API permissions are fixed, set enabled=true
Security:
- logs/ directory excluded from git
- .gitkeep file to preserve directory structure
- Protected by .htaccess (log files already blocked)
Usage:
- All activity automatically logged
- View reports at /logs-viewer.php
- Export logs as CSV for analysis
- Filter by action type, user, or time period
- Monitor system health and usage patterns
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added 'enabled' flag to omg_api config (default: false).
Added 'fallback_business_unit' for when OMG API is disabled or fails.
Current API key returns 403 'Access to this API has been disallowed'.
Need to request proper permissions from OMG team for /loreal/v1/getProject endpoint.
For now, set enabled=false to use fallback business unit 'TESTING'.
When API key is updated with proper permissions, set enabled=true.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from 'Authorization: Bearer' to 'X-API-Key' header format.
Removed duplicate Authorization header.
This is the correct format for OMG API authentication.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Sending both X-API-Key and Authorization headers to see which one works.
Added header logging to troubleshoot auth issue.
The API is responding with 401 'Authorization field missing', so we need to
find the correct header format.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed two issues:
1. Removed 'Bearer' prefix from Authorization header (OMG API expects just the key)
2. Fixed private property access error (hardcoded endpoint URL instead of accessing $omgService->config)
OMG API now sends: Authorization: PeyJvcmciOiIy...
Instead of: Authorization: Bearer PeyJvcmciOiIy...
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Issue: PHP deprecation warnings from league/csv were being output as HTML
(<br /><b>...) which broke JSON parsing in browser.
Fix: Set display_errors=0 in process-csv.php while keeping log_errors=1.
Errors still logged to error_log but not output to browser.
Reverted test-upload endpoint back to process-csv.php.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added log message at start to confirm correct version is running.
This helps identify if browser is caching old JavaScript or server is serving old PHP.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
OMG API Debug Logging:
- Log full URL being called
- Log API key (first 20 chars for security)
- Log HTTP response code
- Log response body (first 500 chars)
- Log campaign number and business area extraction
- Log business unit mapping result
ApplicationLogger Class:
- Structured JSON logging to logs/application.log
- Track all actions: master_asset_submission, global_to_local_transform, box_upload
- Capture user email, timestamp, IP address, user agent
- Methods for reporting: getRecentLogs(), getLogsByAction(), getLogsByUser()
- Generate statistics: total actions, by user, by action, errors
Email Configuration:
- Configured SMTP via Mailgun (smtp.mailgun.org:587)
- Using twist@mail.dev.oliver.solutions
- Emails sent to logged-in user
This enables full audit trail and troubleshooting capability.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Email Configuration:
- Added SMTP support via Mailgun (smtp.mailgun.org:587)
- EmailService now supports both Mailgun API and SMTP
- Configured to use twist@mail.dev.oliver.solutions
- Emails sent to logged-in user (SSO email or local dev email)
OMG API:
- Enabled OMG API lookup in process-csv.php
- API key configured in config.php
- Looks up business unit from campaign number
- Falls back to 'ERROR' if business unit not recognized
SMTP Implementation:
- Full SMTP protocol with AUTH LOGIN
- Proper error handling and logging
- Fallback to Mailgun API if SMTP fails
Notifications sent to user email:
- Process started notification
- Process completed notification (with file count)
- Error notifications
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Issue: PHP's iterator_to_array() returns associative array with numeric keys
which JavaScript receives as an object {1: {...}, 2: {...}}, not an array.
Fix: Convert object to array using Object.values() before rendering table.
Added to both initial preview and file selector preview.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added console.log statements to track:
- Preview data availability
- Row count
- Headers extraction
- Table HTML generation
This will help identify why the table isn't rendering.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed {country} to {$country} in filename template.
Now generates correct filenames like:
- OMG1601654_GlobalACIngest_TESTING-GB_1763418477.csv
- OMG1601654_GlobalACIngest_TESTING-ES_1763418477.csv
etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Initial preview now displays immediately from response data
- Show ALL rows in preview table (not limited to 20)
- File selector properly switches between all 16 CSVs
- Each file shows complete data when selected
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added comprehensive documentation:
- Updated app title to 'L'Oréal OMG Assistant Global'
- Detailed Global to Local workflow (6 stages)
- Input CSV requirements and format
- ISO codes configuration (16 markets in config.php)
- Output file naming and structure
- Preview and download options
- User approval workflow
- Error handling examples for all stages
- API endpoints documentation
- Configuration sections for OMG API and Email
Clarified that ISO codes are in config.php and fully editable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Title Changes:
- App title: "L'Oréal OMG Assistant Global"
- Tab 1: "Master Global Asset Submission"
- Upload button: "Approve & Upload to OMG"
Preview Enhancements:
- Dropdown selector to preview all 16 CSV files individually
- Shows filename with ISO code (e.g., "en-GB - OMG1601654_...")
- Switch between files to view complete data for each
- Show ALL rows (not just first 20)
Download Features:
- "Download Current File" - Download the currently previewed CSV
- "Download All Files (ZIP)" - Download all 16 CSVs as a ZIP archive
- get-csv-preview.php: Endpoint to fetch any file for preview
- download-all-csv.php: Creates ZIP with all CSVs
UX Improvements:
- File selector styled with brand colors
- Clear labeling of which file is being previewed
- Easy navigation between all regional CSVs
- Test before upload with full data visibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Line 180 had */" which caused PHP parse error.
Changed to just */ to close comment block properly.
This was causing the 500 Internal Server Error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added error_log debugging to process-csv.php
- Fixed test-csv.php syntax (removed use statements in code)
- Created test-process2.php for step-by-step class loading test
- All service classes load successfully in tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed 'use' statements outside namespace context.
Changed to fully qualified class names:
- League\Csv\Reader
- League\Csv\Writer
- Carbon\Carbon
This fixes PHP 500 errors from improper use statements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Moved session_start() to top of process-csv.php before any output.
Removed duplicate session_start() call.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Key Changes:
- Fixed CSV transformation to create 16 separate files (one per ISO code)
- Each CSV has all input rows with Language/Country replaced
- Handles Excel Sep=, prefix correctly
- Parses date format "24 Mar 2025 00:00" from sample CSV
- Skip OMG API for testing (use business unit "TESTING")
- Upload all 16 CSVs to Box folder
- Show list of all files in preview
- Download preview functionality
PHP Compatibility:
- Downgraded nesbot/carbon to ^2.0 (supports PHP 7.1+)
- Downgraded league/csv to 9.8.0 (supports PHP 7.4+)
- Now compatible with PHP 7.4-8.0 servers
Preview Enhancements:
- Shows all 16 filenames with ISO codes
- Preview table shows first file as sample
- Summary shows file count and total rows
Testing:
- Added sample CSV: Project_1601654_mediaBookings.csv
- OMG API lookup commented out (ready to enable later)
- Using "TESTING" as business unit
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Downgraded dependencies to support PHP 7.4+:
- nesbot/carbon: ^2.0 (PHP 7.1+)
- league/csv: 9.8.0 (PHP 7.4+)
This ensures the application works on servers with PHP 7.4-8.0.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Services Created:
- OMGService.php: OMG API integration with detailed error handling
- CSVTransformer.php: CSV parsing and transformation logic
- EmailService.php: Mailgun email notifications
- process-csv.php: Multi-stage CSV processing with progress tracking
- upload-to-box.php: Box upload with approval workflow
Features:
- Comprehensive validation at each stage (upload, parse, campaign, API, transform)
- Detailed error reporting with actionable messages
- Warning system for non-critical issues
- Progress tracking through all stages
- Session-based CSV storage for preview before upload
- Date transformation (parse + add 1 month per blueprint)
- 16x market multiplication per ISO codes
- Business unit mapping per Make.com blueprint logic
Dependencies Added:
- league/csv for CSV parsing
- nesbot/carbon for date manipulation
Configuration:
- Added global_to_local settings (ISO codes, business unit map)
- Added omg_api settings (placeholder for API key)
- Added email settings (Mailgun placeholders)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added detailed sections:
- Workflow Overview and Purpose
- Box folder structure requirements with visual diagram
- Complete data extraction breakdown (Box API, User Input, System)
- Step-by-step workflow (7 detailed steps)
- Data flow diagram
- Example webhook payload with full structure
- What happens after submission
- Comprehensive error handling documentation
This provides complete understanding of how the application extracts and processes campaign asset metadata.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Structure: Campaign Number → CAMPAIGN_ASSETS → SUPPLIED_ASSETS
- Changed to fetch parent, then parent's parent (proper two levels up)
- Previous logic was using path_collection which showed CAMPAIGN_ASSETS
- Now correctly retrieves the campaign number folder
- Uses two separate API calls to traverse up the hierarchy
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from grandparent (two levels up) to parent (one level up).
- Renamed getGrandparentFolder() to getParentFolder()
- Uses folder's direct parent instead of path_collection traversal
- Master Campaign Number should now show the campaign number folder
- Updated validate-box.php to use 'parent' instead of 'grandparent'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed align-items to stretch for proper vertical alignment
- Added flex: 1 to input for proper width distribution
- Added flex-shrink: 0 to button to prevent shrinking
- Increased min-width to 120px for 'Looking up...' text
- Fixed transform on disabled state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Check folder name immediately after fetching folder info
- Stop processing if folder name is not SUPPLIED_ASSETS (case-insensitive)
- Return clear error message with actual folder name
- Prevents unnecessary API calls for invalid folders
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Make.com webhooks use the URL itself as authentication, not custom headers.
Removed 'Loreal-Webhook' header - only sending Content-Type: application/json.
Added payload logging for debugging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Issue: When webhook returned 401, PHP was setting http_response_code(401)
which made the browser think the PHP endpoint was unauthorized.
Fix: Always return 200 to client, but indicate webhook failure in JSON response.
Added detailed logging of webhook failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Display user email in top right of header bar
- Update header layout with flexbox
- Show email in yellow (#FFC407) color
- Always visible regardless of SSO mode
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Enable display_errors for debugging
- Add SSO status logging
- Add detailed error information in response
- Log authentication user details
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>