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>
212 lines
7.2 KiB
PHP
212 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* Upload CSV to Box
|
|
* Uploads the processed CSV file to Box after user approval
|
|
*/
|
|
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', 1);
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
try {
|
|
// Load dependencies
|
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
require_once __DIR__ . '/AuthMiddleware.php';
|
|
require_once __DIR__ . '/BoxService.php';
|
|
require_once __DIR__ . '/EmailService.php';
|
|
require_once __DIR__ . '/ApplicationLogger.php';
|
|
|
|
// Authenticate
|
|
$auth = new AuthMiddleware();
|
|
$user = $auth->requireAuth();
|
|
|
|
// Initialize logger
|
|
$logger = new ApplicationLogger();
|
|
|
|
// Check if processed data exists in session
|
|
session_start();
|
|
|
|
if (!isset($_SESSION['processed_csv'])) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'stage' => 'session',
|
|
'error' => 'No processed data found',
|
|
'details' => 'Session expired or CSV not processed',
|
|
'action' => 'Please upload and process the CSV again'
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
$processedData = $_SESSION['processed_csv'];
|
|
$csvFiles = $processedData['files'];
|
|
$campaignNumber = $processedData['campaignNumber'];
|
|
$businessUnit = $processedData['businessUnit'];
|
|
$fileCount = $processedData['fileCount'];
|
|
|
|
// Load config
|
|
$appConfig = require __DIR__ . '/config.php';
|
|
$outputFolderId = $appConfig['global_to_local']['output_box_folder_id'];
|
|
|
|
// Validate output folder is configured
|
|
if ($outputFolderId === 'XXXXXXXXX' || empty($outputFolderId)) {
|
|
echo json_encode([
|
|
'success' => false,
|
|
'stage' => 'config',
|
|
'error' => 'Output Box folder not configured',
|
|
'details' => 'The output_box_folder_id is not set in config.php',
|
|
'action' => 'Contact administrator to configure Box output folder'
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// STAGE 7: Upload all CSVs to Box
|
|
$boxService = new BoxService();
|
|
$uploadedFiles = [];
|
|
$uploadErrors = [];
|
|
|
|
error_log("Uploading {$fileCount} CSV files to Box folder: {$outputFolderId}");
|
|
|
|
try {
|
|
// Authenticate with Box
|
|
$token = $boxService->authenticate();
|
|
|
|
// Upload each CSV file
|
|
foreach ($csvFiles as $csvFile) {
|
|
$filename = $csvFile['filename'];
|
|
$csvContent = $csvFile['content'];
|
|
|
|
try {
|
|
$url = 'https://upload.box.com/api/2.0/files/content';
|
|
|
|
$boundary = uniqid();
|
|
$delimiter = '-------------' . $boundary;
|
|
|
|
$postData = '';
|
|
$postData .= "--{$delimiter}\r\n";
|
|
$postData .= 'Content-Disposition: form-data; name="attributes"' . "\r\n\r\n";
|
|
$postData .= json_encode([
|
|
'name' => $filename,
|
|
'parent' => ['id' => $outputFolderId]
|
|
]) . "\r\n";
|
|
|
|
$postData .= "--{$delimiter}\r\n";
|
|
$postData .= 'Content-Disposition: form-data; name="file"; filename="' . $filename . '"' . "\r\n";
|
|
$postData .= 'Content-Type: text/csv' . "\r\n\r\n";
|
|
$postData .= $csvContent . "\r\n";
|
|
$postData .= "--{$delimiter}--\r\n";
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => $postData,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: Bearer ' . $token,
|
|
'Content-Type: multipart/form-data; boundary=' . $delimiter,
|
|
'Content-Length: ' . strlen($postData)
|
|
],
|
|
CURLOPT_TIMEOUT => 60
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curlError = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($curlError) {
|
|
throw new Exception('Upload failed: ' . $curlError);
|
|
}
|
|
|
|
if ($httpCode !== 201) {
|
|
$responseData = json_decode($response, true);
|
|
throw new Exception('HTTP ' . $httpCode . ': ' . ($responseData['message'] ?? $response));
|
|
}
|
|
|
|
$uploadedFile = json_decode($response, true);
|
|
$fileId = $uploadedFile['entries'][0]['id'] ?? null;
|
|
|
|
$uploadedFiles[] = [
|
|
'filename' => $filename,
|
|
'fileId' => $fileId,
|
|
'isoCode' => $csvFile['isoCode']
|
|
];
|
|
|
|
error_log("Uploaded: {$filename} (ID: {$fileId})");
|
|
|
|
} catch (Exception $fileError) {
|
|
error_log("Failed to upload {$filename}: " . $fileError->getMessage());
|
|
$uploadErrors[] = [
|
|
'filename' => $filename,
|
|
'error' => $fileError->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
// Check if all uploads succeeded
|
|
if (count($uploadErrors) > 0) {
|
|
$successCount = count($uploadedFiles);
|
|
throw new Exception("Uploaded {$successCount}/{$fileCount} files. " . count($uploadErrors) . " failed.");
|
|
}
|
|
|
|
// Send completion email
|
|
$emailService->notifyCompleted($user['email'], [
|
|
'campaign_number' => $campaignNumber,
|
|
'business_unit' => $businessUnit,
|
|
'input_rows' => $processedData['inputRowCount'],
|
|
'file_count' => $fileCount,
|
|
'total_rows' => $processedData['inputRowCount'] * $fileCount,
|
|
'folder_url' => $folderUrl
|
|
]);
|
|
|
|
// Log successful upload
|
|
$logger->logBoxUpload($user, $fileCount, $outputFolderId, 'success');
|
|
|
|
// Clear session data
|
|
unset($_SESSION['processed_csv']);
|
|
|
|
$folderUrl = 'https://app.box.com/folder/' . $outputFolderId;
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'stage' => 'complete',
|
|
'message' => "Successfully uploaded {$fileCount} CSV files to Box",
|
|
'data' => [
|
|
'uploadedFiles' => $uploadedFiles,
|
|
'fileCount' => $fileCount,
|
|
'folderUrl' => $folderUrl
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Box upload exception: ' . $e->getMessage());
|
|
|
|
// Send error email
|
|
$emailService->notifyError($user['email'], [
|
|
'filename' => $processedData['files'][0]['filename'] ?? 'Unknown',
|
|
'campaign_number' => $campaignNumber,
|
|
'stage' => 'box_upload',
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
echo json_encode([
|
|
'success' => false,
|
|
'stage' => 'box_upload',
|
|
'error' => 'Failed to upload CSV to Box',
|
|
'details' => $e->getMessage(),
|
|
'action' => 'Check Box folder permissions and try again'
|
|
]);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
error_log('Upload exception: ' . $e->getMessage());
|
|
|
|
echo json_encode([
|
|
'success' => false,
|
|
'stage' => 'exception',
|
|
'error' => 'Unexpected error occurred',
|
|
'details' => $e->getMessage(),
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine()
|
|
]);
|
|
}
|