From 15accf97b695d895caafa3bf9f37391945340dac Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Fri, 5 May 2023 19:55:00 +0200 Subject: [PATCH] wip #5819 @1 --- .../Services/BastideOperation.php | 252 ++++++++++++++++++ app/Models/FluidbookPublication.php | 15 +- 2 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/Admin/Operations/FluidbookPublication/Services/BastideOperation.php diff --git a/app/Http/Controllers/Admin/Operations/FluidbookPublication/Services/BastideOperation.php b/app/Http/Controllers/Admin/Operations/FluidbookPublication/Services/BastideOperation.php new file mode 100644 index 000000000..a097bc513 --- /dev/null +++ b/app/Http/Controllers/Admin/Operations/FluidbookPublication/Services/BastideOperation.php @@ -0,0 +1,252 @@ +withoutMiddleware([CheckIfAdmin::class, VerifyCsrfToken::class]); + } + } + + /** + * @throws Exception + */ + public function bastide() + { + if (empty(request('cart_items'))) { + die('Error: invalid data received'); + } + + $user_details = json_decode(request('user_details'), true); + $cart_items = json_decode(request('cart_items'), true); + $column_headings = json_decode(request('column_headings'), true); + + $excel = new Spreadsheet(); + $excel->setActiveSheetIndex(0); + $defaultStyle = $excel->getDefaultStyle(); + $defaultStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER); + $defaultStyle->getAlignment()->setWrapText(true); + $defaultStyle->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_TEXT); + $sheet = $excel->getActiveSheet(); + $sheet->setTitle('Récapitulatif de demande'); + $sheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); + $sheet->getPageSetup()->setFitToPage(true); + + // Setup column headings + $current_column_index = 1; + $current_row = 1; + $sheet->getRowDimension(1)->setRowHeight(22); // Larger row height for headings + foreach ($column_headings as $column_heading) { + $sheet->setCellValueByColumnAndRow($current_column_index, $current_row, $column_heading); + $cell_style = $sheet->getStyleByColumnAndRow($current_column_index, $current_row); + $cell_style->getFont()->setBold(true); + $cell_style->getBorders()->getBottom()->setBorderStyle(Border::BORDER_MEDIUM); + $current_column_index++; + } + + // Output cart data + foreach ($cart_items as $cart_item) { + $current_row++; + + // Set a slightly larger row height to give better vertical spacing + $sheet->getRowDimension($current_row)->setRowHeight(22); + + // Look up the value by the column keys to ensure the correct order of values + foreach (array_keys($column_headings) as $column_index => $column_key) { + + $current_cell_style = $sheet->getStyleByColumnAndRow($column_index + 1, $current_row); + + switch ($column_key) { + case 'ARTICLE CODE': // Explicitly store ARTICLE CODE as text in order to preserve any leading zeros + $sheet->setCellValueExplicitByColumnAndRow($column_index + 1, $current_row, $cart_item[$column_key], DataType::TYPE_STRING); + break; + case 'QUANTITY': + $sheet->setCellValueByColumnAndRow($column_index + 1, $current_row, $cart_item[$column_key]); + $current_cell_style->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_NUMBER); + break; + default: + $sheet->setCellValueByColumnAndRow($column_index + 1, $current_row, $cart_item[$column_key]); + } + + } + } + + // Autosize columns once the data has been added + for ($i = 1; $i <= count($column_headings); $i++) { + $sheet->getColumnDimensionByColumn($i)->setAutoSize(true); + } + + //================================ + // Add user details in a new sheet + // The sheet must be added to the workbook before we can access the cell styles, otherwise it fails silently :( + $details_sheet = $excel->addSheet(new Worksheet($excel, 'Coordonnées'), 0); // Setting index to zero will put this sheet first in the workbook + + $current_row = 1; + foreach ($user_details as $user_detail) { + // User details are displayed as Label: Value in the A & B columns + $details_label_style = $details_sheet->getStyleByColumnAndRow(1, $current_row); + $details_label_style->getFont()->setBold(true); + $details_label_style->getAlignment()->setWrapText(false); + $details_label_style->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); + $details_label_style->getAlignment()->setVertical(Alignment::VERTICAL_TOP); + + // Top align the values because we'll be using a slightly larger row height + $details_value_style = $details_sheet->getStyleByColumnAndRow(2, $current_row); + $details_value_style->getAlignment()->setVertical(Alignment::VERTICAL_TOP); + + // Set values + $details_sheet->setCellValueByColumnAndRow(1, $current_row, $user_detail['label'] . ' :'); + $details_sheet->setCellValueByColumnAndRow(2, $current_row, $user_detail['value']); + + // $details_sheet->getStyleByColumnAndRow(1, $current_row)->getAlignment()->setWrapText(true); + // $details_sheet->getRowDimension($current_row)->setRowHeight(-1); // Auto row height from content + + // Unfortunately, LibreOffice has a long-standing bug where auto row height doesn't work :( + // Ref: https://github.com/PHPOffice/PHPExcel/issues/588#issuecomment-249544915 + // Since we may have multi-line text for the address and message, we need to figure out how high + // the row should be, based on the number of lines. The default row height is 12.75pt + // Ref: https://github.com/PHPOffice/PHPExcel/blob/1.8/Documentation/markdown/Overview/08-Recipes.md#setting-a-rows-height + $single_row_height = 12.75; // Basis for a single line of text + $line_count = count(explode("\n", $user_detail['value'])); + // Add a slight extra buffer to row height (+3) so it isn't tight against the next one + $details_sheet->getRowDimension($current_row)->setRowHeight($line_count * $single_row_height + 3); + + $current_row++; + } + + // Auto-size both columns + $details_sheet->getColumnDimensionByColumn(1)->setAutoSize(true); + $details_sheet->getColumnDimensionByColumn(2)->setAutoSize(true); + + //==== Save the Excel spreadsheet to disk temporarily + $xls_writer = new Xlsx($excel); + $xls_file = Files::tempnam(); + $xls_writer->save($xls_file); + + //==== Convert the XLSX file to PDF for the client + $libreoffice = new CommandLine('libreoffice', false, false); + $libreoffice->setArg('--convert-to', 'pdf'); + // libreoffice doesn't allow us to set a destination name (it's a batch conversion command), + // so make sure the file is written back to the same temporary path: + $libreoffice->setArg('--outdir', sys_get_temp_dir()); + $libreoffice->setArg(null, $xls_file); + $libreoffice->execute(); + + $pdf_file = "{$xls_file}.pdf"; // Filename of converted file should be the original + .pdf + + //==== Prepare the e-mail + // The Fluidbook can pass on a parameter that overrides the default recipient + $recipient_code = request('recipient_code', ''); + + //$default_bastide_address = 'web@hcm-medical.fr'; // Fallback address + destination for all BCC messages + $default_bastide_address = 'test+bastide@cubedesigners.com'; // Fallback address + destination for all BCC messages + + switch (strtolower($recipient_code)) { + case 'co-guadeloupe': + $email_for_bastide = 'commercial@bastide-guadeloupe.com'; + break; + case 'gp-guadeloupe': + $email_for_bastide = 'magasin@bastide-guadeloupe.com'; + break; + case 'pro-guadeloupe': + $email_for_bastide = 'contact@bastide-guadeloupe.com'; + break; + case 'gp-martinique': + $email_for_bastide = 'magasin@bastide-martinique.com'; + break; + case 'pro-martinique': + $email_for_bastide = 'adv@bastide-martinique.com'; + break; + case 'gp-réunion': + case 'gp-reunion': + case 'pro-réunion': + case 'pro-reunion': + $email_for_bastide = 'reseau@bastide.re'; + break; + default: + $email_for_bastide = $default_bastide_address; + } + + $return_address = 'postmaster@fluidbook.com'; + $email_from_name = "Bastide"; + $email_reply_to = $email_for_bastide; + $email_subject = 'Récapitulatif de votre demande Bastide'; + $email_to_user_body = "Bonjour,

Votre demande est en cours de traitement (récapitulatif en pièce jointe). Nous reviendrons vers vous avec un devis dans les meilleurs délais.

Cordialement,

L'équipe Bastide"; + $email_to_bastide_body = "Bonjour,

Une commande a été passée (récapitulatif en pièce jointe).

Cordialement,

L'équipe Bastide"; + + // Filename for attachment (without extension) + $orderId = date('YmdHis'); + $attachment_basename = 'Bastide-' . $orderId; + + //=== Send the email + // There are two almost identical e-mails sent: + // - E-mail to Bastide, containing the request details in XLS format + // - E-mail to the user, containing the same information but converted to PDF format + $mail_to_bastide = new Base(); + $mail_to_bastide->from($return_address, $email_from_name); + $mail_to_bastide->replyTo($email_reply_to, $email_from_name); + $mail_to_bastide->subject('Nouvelle commande ' . $orderId); + $mail_to_bastide->html($email_to_bastide_body); + + // Duplicate the common e-mail settings and apply them to the user e-mail + $mail_to_user = clone $mail_to_bastide; + $mail_to_user->to($user_details['email']['value']); + $mail_to_user->subject($email_subject); + $mail_to_user->attach($pdf_file, ['as' => $attachment_basename . '.pdf', 'mime' => 'application/pdf']); + $mail_to_user->html($email_to_user_body); + + // Back to setting up the mail for Bastide + $mail_to_bastide->to($email_for_bastide); + $mail_to_bastide->attach($xls_file, ['as' => $attachment_basename . '.xlsx', 'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']); + + // When the recipient isn't the default Bastide address, we add them in as a BCC so they can track the messages + // See: https://redmine.cubedesigners.com/issues/5345#note-36 + if ($email_for_bastide !== $default_bastide_address) { + $mail_to_bastide->bcc($default_bastide_address); + } + + //=== Send the e-mails + try { + Mail::send($mail_to_bastide); + Mail::send($mail_to_user); + $result['success'] = true; + } catch (Exception $e) { + Log::warn('#### Bastide: Failed sending message via Mailjet ####'); + Log::warn('ERROR: ' . $e->getMessage()); + $result['success'] = false; + } + + // Tidy up temporary files after sending + unlink($xls_file); + unlink($pdf_file); + + if ($result['success']) { + $result['message'] = 'Merci, votre demande a été bien reçue. Nous reviendrons vers vous avec un devis dans les meilleurs délais.'; + } else { + $result['message'] = "Désolé, une erreur s'est produite. Veuillez réessayer ou contactez {$email_reply_to}."; + } + + return response()->json($result); + } +} diff --git a/app/Models/FluidbookPublication.php b/app/Models/FluidbookPublication.php index 00afd8062..f62bfeffc 100644 --- a/app/Models/FluidbookPublication.php +++ b/app/Models/FluidbookPublication.php @@ -19,6 +19,7 @@ use App\Http\Controllers\Admin\Operations\FluidbookPublication\DownloadOperation use App\Http\Controllers\Admin\Operations\FluidbookPublication\EditOperation; use App\Http\Controllers\Admin\Operations\FluidbookPublication\LinksOperation; use App\Http\Controllers\Admin\Operations\FluidbookPublication\PreviewOperation; +use App\Http\Controllers\Admin\Operations\FluidbookPublication\Services\BastideOperation; use App\Http\Controllers\Admin\Operations\FluidbookPublication\Services\ExportPdfOperation; use App\Http\Controllers\Admin\Operations\FluidbookPublication\Services\SocialImageOperation; use App\Http\Controllers\Admin\Operations\FluidbookPublication\StatsOperation; @@ -63,7 +64,19 @@ class FluidbookPublication extends ToolboxSettingsModel protected $_operations = [CreateOperation::class, PreviewOperation::class, LinksOperation::class, - CompositionOperation::class, StatsOperation::class, DownloadOperation::class, CloneOperation::class, DeletefbOperation::class, EditOperation::class, ChangeownerOperation::class, SocialImageOperation::class, ExportPdfOperation::class]; + CompositionOperation::class, + StatsOperation::class, + DownloadOperation::class, + CloneOperation::class, + DeletefbOperation::class, + EditOperation::class, + ChangeownerOperation::class, + // Services + SocialImageOperation::class, + ExportPdfOperation::class, + // Carts + BastideOperation::class, + ]; protected $casts = ['composition' => 'array']; -- 2.39.5