]> _ Git - fluidbook-toolbox.git/commitdiff
wip #5819 @1
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Fri, 5 May 2023 17:55:00 +0000 (19:55 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Fri, 5 May 2023 17:55:00 +0000 (19:55 +0200)
app/Http/Controllers/Admin/Operations/FluidbookPublication/Services/BastideOperation.php [new file with mode: 0644]
app/Models/FluidbookPublication.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 (file)
index 0000000..a097bc5
--- /dev/null
@@ -0,0 +1,252 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Operations\FluidbookPublication\Services;
+
+use App\Http\Middleware\CheckIfAdmin;
+use App\Http\Middleware\VerifyCsrfToken;
+use App\Mail\Base;
+use Cubist\Util\CommandLine;
+use Cubist\Util\Files\Files;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Facades\Route;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+trait BastideOperation
+{
+    protected function setupBastideRoutes($segment, $routeName, $controller)
+    {
+        foreach (['services', 's'] as $s) {
+            Route::match(['post'], $s . '/bastide', $controller . '@bastide')->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,<br><br>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.<br><br>Cordialement,<br><br>L'équipe Bastide";
+        $email_to_bastide_body = "Bonjour,<br><br>Une commande a été passée (récapitulatif en pièce jointe).<br><br>Cordialement,<br><br>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);
+    }
+}
index 00afd80623cfc0a4a6da30cd60ed8844fa6c7805..f62bfeffcb4ce99b7362b4e1cb8bfeb44f17fbe2 100644 (file)
@@ -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'];