diff --git a/CSVTransformer.php b/CSVTransformer.php index 375d160..720527a 100644 --- a/CSVTransformer.php +++ b/CSVTransformer.php @@ -213,11 +213,20 @@ class CSVTransformer { foreach ($rows as $inputRow) { $rowNumber++; + // Store original title for Creative Execution + $originalTitle = $inputRow['Title'] ?? ''; + // Replace Language and Country fields $outputRow = $inputRow; $outputRow['Language'] = $language; $outputRow['Country'] = $country; + // Add language/country to Title (append to end) + $outputRow['Title'] = $originalTitle . '_' . $language; + + // Add Creative Execution column (contains the original global title) + $outputRow['Creative Execution'] = $originalTitle; + // Transform dates if needed (add 1 month per blueprint) if (isset($outputRow['Supply date'])) { $outputRow['Supply date'] = $this->transformDate($outputRow['Supply date'], $rowNumber, 'Supply date'); diff --git a/EmailService.php b/EmailService.php index bee3028..57377f3 100644 --- a/EmailService.php +++ b/EmailService.php @@ -12,12 +12,15 @@ class EmailService { $appConfig = require __DIR__ . '/config.php'; $this->config = $appConfig['email']; $this->enabled = $this->config['enabled'] ?? false; + + // Load email templates + require_once __DIR__ . '/EmailTemplates.php'; } /** * Send email via Mailgun API or SMTP */ - public function send($to, $subject, $text) { + public function send($to, $subject, $text, $html = null) { if (!$this->enabled) { error_log('Email not sent (service disabled): ' . $subject); return ['success' => true, 'message' => 'Email disabled']; @@ -26,12 +29,28 @@ class EmailService { $service = $this->config['service'] ?? 'mailgun'; if ($service === 'smtp') { - return $this->sendViaSMTP($to, $subject, $text); + return $this->sendViaSMTP($to, $subject, $text, $html); } else { - return $this->sendViaMailgunAPI($to, $subject, $text); + return $this->sendViaMailgunAPI($to, $subject, $text, $html); } } + /** + * Send templated email + */ + public function sendTemplate($to, $templateName, $data) { + $html = EmailTemplates::getTemplate($templateName, $data); + + // Extract subject from HTML title + preg_match('/]*>(.*?)<\/h1>/', $html, $matches); + $subject = $matches[1] ?? 'L\'Oréal OMG Assistant Notification'; + + // Create plain text version from data + $text = strip_tags($subject) . "\n\n" . json_encode($data, JSON_PRETTY_PRINT); + + return $this->send($to, $subject, $text, $html); + } + /** * Send email via Mailgun API */ @@ -101,7 +120,7 @@ class EmailService { /** * Send email via SMTP */ - private function sendViaSMTP($to, $subject, $text) { + private function sendViaSMTP($to, $subject, $text, $html = null) { try { $from = $this->config['from']; $host = $this->config['smtp_host']; @@ -109,12 +128,19 @@ class EmailService { $username = $this->config['smtp_username']; $password = $this->config['smtp_password']; - // Build email message + // Build email message with multipart if HTML provided + $boundary = md5(time()); + $headers = "From: {$from}\r\n"; $headers .= "Reply-To: {$from}\r\n"; $headers .= "X-Mailer: PHP/" . phpversion() . "\r\n"; $headers .= "MIME-Version: 1.0\r\n"; - $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; + + if ($html) { + $headers .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n"; + } else { + $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; + } // Create SMTP connection $socket = fsockopen($host, $port, $errno, $errstr, 10); @@ -142,7 +168,21 @@ class EmailService { $message = "Subject: {$subject}\r\n"; $message .= $headers; $message .= "\r\n"; - $message .= $text . "\r\n"; + + if ($html) { + // Multipart message + $message .= "--{$boundary}\r\n"; + $message .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n"; + $message .= $text . "\r\n\r\n"; + $message .= "--{$boundary}\r\n"; + $message .= "Content-Type: text/html; charset=UTF-8\r\n\r\n"; + $message .= $html . "\r\n\r\n"; + $message .= "--{$boundary}--\r\n"; + } else { + // Plain text only + $message .= $text . "\r\n"; + } + $message .= ".\r\n"; $this->smtpCommand($socket, $message, 250); @@ -196,34 +236,35 @@ class EmailService { /** * Send process started notification */ - public function notifyStarted($userEmail, $filename) { - $subject = 'Regional Hotfolder - Started'; - $text = "Document: $filename has been picked up for processing."; - - return $this->send($userEmail, $subject, $text); + public function notifyStarted($userEmail, $data) { + return $this->sendTemplate($userEmail, 'global_to_local_started', $data); } /** * Send process completed notification */ - public function notifyCompleted($userEmail, $filename, $outputCount) { - $subject = 'Regional Hotfolder - Completed'; - $text = "Document: $filename has completed processing.\n\n"; - $text .= "Generated $outputCount regional CSV files.\n"; - $text .= "Files should appear in OMG within 5 minutes."; - - return $this->send($userEmail, $subject, $text); + public function notifyCompleted($userEmail, $data) { + return $this->sendTemplate($userEmail, 'global_to_local_complete', $data); } /** * Send error notification */ - public function notifyError($userEmail, $filename, $errorMessage) { - $subject = 'Error - Regional Hotfolder'; - $text = "An error occurred while processing: $filename\n\n"; - $text .= "Error: $errorMessage\n\n"; - $text .= "Please check the file and try again."; + public function notifyError($userEmail, $data) { + return $this->sendTemplate($userEmail, 'global_to_local_failed', $data); + } - return $this->send($userEmail, $subject, $text); + /** + * Send asset submission success notification + */ + public function notifyAssetSubmissionSuccess($userEmail, $data) { + return $this->sendTemplate($userEmail, 'asset_submission_success', $data); + } + + /** + * Send asset submission failed notification + */ + public function notifyAssetSubmissionFailed($userEmail, $data) { + return $this->sendTemplate($userEmail, 'asset_submission_failed', $data); } } diff --git a/EmailTemplates.php b/EmailTemplates.php new file mode 100644 index 0000000..9552ea5 --- /dev/null +++ b/EmailTemplates.php @@ -0,0 +1,283 @@ + + + + + + + +
+
+

{$title}

+
+
+ {$content} +
+ +
+ + +HTML; + } + + /** + * Master Asset Submission - Success + */ + public static function assetSubmissionSuccess($data) { + $content = << +

✅ Master Asset Submission Complete

+ + +
+

Box ID: {$data['box_id']}

+

Campaign Number: {$data['campaign_number']}

+

Folder: {$data['folder_name']}

+

Total Assets: {$data['asset_count']} items

+
+ +

Campaign Dates:

+
+

Supply Date: {$data['supply_date']}

+

Live Date: {$data['live_date']}

+

End Date: {$data['end_date']}

+
+ +
+

✓ Status: Asset metadata successfully submitted to Make.com workflow.

+

Your assets are now being processed in the OMG system.

+
+HTML; + + return self::wrapTemplate('✅ Asset Submission Complete', $content, '#28a745'); + } + + /** + * Master Asset Submission - Failed + */ + public static function assetSubmissionFailed($data) { + $content = << +

❌ Asset Submission Failed

+ + +
+

Box ID: {$data['box_id']}

+

Campaign Number: {$data['campaign_number']}

+
+ +

Error Details:

+
+

Error: {$data['error']}

+
+ +
+

📌 Action Required: Please review the error and try again.

+

If the issue persists, contact the system administrator.

+
+HTML; + + return self::wrapTemplate('❌ Asset Submission Failed', $content, '#d32f2f'); + } + + /** + * Global to Local - Processing Started + */ + public static function globalToLocalStarted($data) { + $content = << +

📄 File: {$data['filename']}

+

Campaign Number: {$data['campaign_number']}

+ + +

Processing Steps:

+
+

✓ File uploaded and validated

+

🔄 Extracting campaign information...

+

🔄 Calling OMG API for business unit...

+

🔄 Transforming data for 16 regional markets...

+
+ +
+

⏳ Please wait: You will receive another email when processing is complete.

+
+HTML; + + return self::wrapTemplate('🔄 CSV Processing Started', $content); + } + + /** + * Global to Local - Processing Complete + */ + public static function globalToLocalComplete($data) { + $content = << +

✅ CSV Transformation Complete

+ + +
+

Campaign Number: {$data['campaign_number']}

+

Business Unit: {$data['business_unit']}

+

Input Rows: {$data['input_rows']}

+

Output Files Created: {$data['file_count']}

+

Total Output Rows: {$data['total_rows']}

+
+ +

Files Created (16 Regional CSVs):

+ + + + + + +HTML; + + // Add sample rows (first 5 files) + $isoSample = ['en-GB', 'es-ES', 'pt-PT', 'en-IE', 'fr-CH']; + foreach ($isoSample as $iso) { + $parts = explode('-', $iso); + $country = $parts[1] ?? ''; + $content .= ""; + } + + $content .= << +
ISO CodeCountryRows
{$iso}{$country}{$data['input_rows']}
... and 11 more files
+ +
+

✓ Complete: All {$data['file_count']} CSV files have been uploaded to Box.

+

Files should appear in OMG within 5 minutes.

+
+ +
+

Box Folder: {$data['folder_url']}

+
+HTML; + + return self::wrapTemplate('✅ Global to Local Complete', $content, '#28a745'); + } + + /** + * Global to Local - Processing Failed + */ + public static function globalToLocalFailed($data) { + $content = << +

❌ CSV Processing Failed

+ + +
+

File: {$data['filename']}

+ {$data['campaign_number'] ? "

Campaign Number: {$data['campaign_number']}

" : ''} +
+ +

Error Details:

+
+

Stage: {$data['stage']}

+

Error: {$data['error']}

+
+ +
+

📌 Action Required:

+ +
+HTML; + + return self::wrapTemplate('❌ CSV Processing Failed', $content, '#d32f2f'); + } + + /** + * Box Upload - Success + */ + public static function boxUploadSuccess($data) { + $content = << +

✅ Files Uploaded to Box Successfully

+ + +
+

Files Uploaded: {$data['file_count']}

+

Campaign Number: {$data['campaign_number']}

+

Business Unit: {$data['business_unit']}

+
+ +

Upload Summary:

+
+

All {$data['file_count']} regional CSV files have been successfully uploaded to the Box output folder.

+

These files are now available in the OMG system for further processing.

+
+ +
+

Box Folder: View Files in Box

+
+HTML; + + return self::wrapTemplate('✅ Box Upload Complete', $content, '#28a745'); + } + + /** + * Get template by name + */ + public static function getTemplate($templateName, $data) { + switch ($templateName) { + case 'asset_submission_success': + return self::assetSubmissionSuccess($data); + + case 'asset_submission_failed': + return self::assetSubmissionFailed($data); + + case 'global_to_local_started': + return self::globalToLocalStarted($data); + + case 'global_to_local_complete': + return self::globalToLocalComplete($data); + + case 'global_to_local_failed': + return self::globalToLocalFailed($data); + + case 'box_upload_success': + return self::boxUploadSuccess($data); + + default: + // Fallback simple template + return self::wrapTemplate('Notification', '

' . json_encode($data) . '

'); + } + } +} diff --git a/upload-to-box.php b/upload-to-box.php index c559e44..cb3cd2d 100644 --- a/upload-to-box.php +++ b/upload-to-box.php @@ -150,7 +150,14 @@ try { } // Send completion email - $emailService->notifyCompleted($user['email'], "{$fileCount} regional CSV files", count($uploadedFiles)); + $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'); @@ -175,7 +182,12 @@ try { error_log('Box upload exception: ' . $e->getMessage()); // Send error email - $emailService->notifyError($user['email'], $filename, $e->getMessage()); + $emailService->notifyError($user['email'], [ + 'filename' => $processedData['files'][0]['filename'] ?? 'Unknown', + 'campaign_number' => $campaignNumber, + 'stage' => 'box_upload', + 'error' => $e->getMessage() + ]); echo json_encode([ 'success' => false,