diff --git a/modules/order/commerce_order.routing.yml b/modules/order/commerce_order.routing.yml index ee9383e4..a7631d2f 100644 --- a/modules/order/commerce_order.routing.yml +++ b/modules/order/commerce_order.routing.yml @@ -45,3 +45,15 @@ entity.commerce_order.user_view: entity: user: 'entity:user' commerce_order: 'entity:order' + +entity.commerce_order.resend_receipt: + path: '/admin/commerce/orders/{commerce_order}/resend' + defaults: + _form: '\Drupal\commerce_order\Form\ResendReceiptForm' + _title: 'Resend receipt' + options: + parameters: + commerce_order: + type: 'entity:commerce_order' + requirements: + _permission: 'administer commerce_order' diff --git a/modules/order/commerce_order.services.yml b/modules/order/commerce_order.services.yml index 8d8e5d49..788efc3e 100644 --- a/modules/order/commerce_order.services.yml +++ b/modules/order/commerce_order.services.yml @@ -45,7 +45,7 @@ services: commerce_order.order_receipt_subscriber: class: Drupal\commerce_order\EventSubscriber\OrderReceiptSubscriber - arguments: ['@entity_type.manager', '@language_manager', '@plugin.manager.mail', '@commerce_order.order_total_summary', '@renderer'] + arguments: ['@commerce_order.order_receipt'] tags: - { name: 'event_subscriber' } @@ -62,3 +62,7 @@ services: arguments: ['@current_route_match'] tags: - { name: commerce_store.store_resolver, priority: 100 } + + commerce_order.order_receipt: + class: Drupal\commerce_order\OrderReceipt + arguments: ['@entity_type.manager', '@language_manager', '@plugin.manager.mail', '@commerce_order.order_total_summary', '@renderer'] diff --git a/modules/order/src/Entity/Order.php b/modules/order/src/Entity/Order.php index 99eb6bef..8b829de0 100644 --- a/modules/order/src/Entity/Order.php +++ b/modules/order/src/Entity/Order.php @@ -62,7 +62,8 @@ use Drupal\profile\Entity\ProfileInterface; * "delete-multiple-form" = "/admin/commerce/orders/delete", * "reassign-form" = "/admin/commerce/orders/{commerce_order}/reassign", * "unlock-form" = "/admin/commerce/orders/{commerce_order}/unlock", - * "collection" = "/admin/commerce/orders" + * "collection" = "/admin/commerce/orders", + * "resend-receipt" = "/admin/commerce/orders/{commerce_order}/resend" * }, * bundle_entity_type = "commerce_order_type", * field_ui_base_route = "entity.commerce_order_type.edit_form" diff --git a/modules/order/src/EventSubscriber/OrderReceiptSubscriber.php b/modules/order/src/EventSubscriber/OrderReceiptSubscriber.php index 850e3363..d6b5cee2 100644 --- a/modules/order/src/EventSubscriber/OrderReceiptSubscriber.php +++ b/modules/order/src/EventSubscriber/OrderReceiptSubscriber.php @@ -2,12 +2,7 @@ namespace Drupal\commerce_order\EventSubscriber; -use Drupal\commerce_order\OrderTotalSummaryInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\Mail\MailManagerInterface; -use Drupal\Core\Render\RenderContext; -use Drupal\Core\Render\Renderer; +use Drupal\commerce_order\OrderReceipt; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\state_machine\Event\WorkflowTransitionEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -20,68 +15,20 @@ class OrderReceiptSubscriber implements EventSubscriberInterface { use StringTranslationTrait; /** - * The order type entity storage. + * The OrderReceipt object. * - * @var \Drupal\Core\Entity\EntityStorageInterface + * @var \Drupal\commerce_order\OrderReceipt */ - protected $orderTypeStorage; - - /** - * The order total summary. - * - * @var \Drupal\commerce_order\OrderTotalSummaryInterface - */ - protected $orderTotalSummary; - - /** - * The entity view builder for profiles. - * - * @var \Drupal\profile\ProfileViewBuilder - */ - protected $profileViewBuilder; - - /** - * The language manager. - * - * @var \Drupal\Core\Language\LanguageManagerInterface - */ - protected $languageManager; - - /** - * The mail manager. - * - * @var \Drupal\Core\Mail\MailManagerInterface - */ - protected $mailManager; - - /** - * The renderer. - * - * @var \Drupal\Core\Render\RendererInterface - */ - protected $renderer; + protected $orderReceipt; /** * Constructs a new OrderReceiptSubscriber object. * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager. - * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager - * The language manager. - * @param \Drupal\Core\Mail\MailManagerInterface $mail_manager - * The mail manager. - * @param \Drupal\commerce_order\OrderTotalSummaryInterface $order_total_summary - * The order total summary. - * @param \Drupal\Core\Render\Renderer $renderer - * The renderer. + * @param \Drupal\commerce_order\OrderReceipt $order_receipt_resend + * Object of OrderReceipt class. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, MailManagerInterface $mail_manager, OrderTotalSummaryInterface $order_total_summary, Renderer $renderer) { - $this->orderTypeStorage = $entity_type_manager->getStorage('commerce_order_type'); - $this->orderTotalSummary = $order_total_summary; - $this->profileViewBuilder = $entity_type_manager->getViewBuilder('profile'); - $this->languageManager = $language_manager; - $this->mailManager = $mail_manager; - $this->renderer = $renderer; + public function __construct(OrderReceipt $order_receipt_resend) { + $this->orderReceipt = $order_receipt_resend; } /** @@ -93,51 +40,7 @@ class OrderReceiptSubscriber implements EventSubscriberInterface { public function sendOrderReceipt(WorkflowTransitionEvent $event) { /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $event->getEntity(); - /** @var \Drupal\commerce_order\Entity\OrderTypeInterface $order_type */ - $order_type = $this->orderTypeStorage->load($order->bundle()); - if (!$order_type->shouldSendReceipt()) { - return; - } - $to = $order->getEmail(); - if (!$to) { - // The email should not be empty, unless the order is malformed. - return; - } - - $params = [ - 'headers' => [ - 'Content-Type' => 'text/html; charset=UTF-8;', - 'Content-Transfer-Encoding' => '8Bit', - ], - 'from' => $order->getStore()->getEmail(), - 'subject' => $this->t('Order #@number confirmed', ['@number' => $order->getOrderNumber()]), - 'order' => $order, - ]; - if ($receipt_bcc = $order_type->getReceiptBcc()) { - $params['headers']['Bcc'] = $receipt_bcc; - } - - $build = [ - '#theme' => 'commerce_order_receipt', - '#order_entity' => $order, - '#totals' => $this->orderTotalSummary->buildTotals($order), - ]; - if ($billing_profile = $order->getBillingProfile()) { - $build['#billing_information'] = $this->profileViewBuilder->view($billing_profile); - } - $params['body'] = $this->renderer->executeInRenderContext(new RenderContext(), function () use ($build) { - return $this->renderer->render($build); - }); - - // Replicated logic from EmailAction and contact's MailHandler. - if ($customer = $order->getCustomer()) { - $langcode = $customer->getPreferredLangcode(); - } - else { - $langcode = $this->languageManager->getDefaultLanguage()->getId(); - } - - $this->mailManager->mail('commerce_order', 'receipt', $to, $langcode, $params); + $this->orderReceipt->sendOrderReceipt($order); } /** diff --git a/modules/order/src/Form/ResendReceiptForm.php b/modules/order/src/Form/ResendReceiptForm.php new file mode 100644 index 00000000..47f2b283 --- /dev/null +++ b/modules/order/src/Form/ResendReceiptForm.php @@ -0,0 +1,94 @@ +orderReceipt = $order_receipt_resend; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('commerce_order.order_receipt') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'commerce_order_resend_receipt_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, OrderInterface $commerce_order = NULL) { + $this->order = $commerce_order; + $form['confirm'] = [ + '#markup' => t('Are you sure you want to resend the confirmation email?'), + ]; + + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Resend confirmation'), + '#button_type' => 'primary', + ]; + $form['actions']['cancel'] = [ + '#type' => 'link', + '#title' => $this->t('Cancel'), + '#attributes' => ['class' => ['button']], + '#url' => Url::fromRoute('entity.commerce_order.user_view', ['user' => $this->order->getCustomerId(), 'commerce_order' => $this->order->id()]), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + if ($this->orderReceipt->sendOrderReceipt($this->order)) { + drupal_set_message($this->t('Email confirmation resent successfully.')); + } + else { + drupal_set_message($this->t('Email confirmation could not be resent, please contact site administrator.')); + } + } + +} diff --git a/modules/order/src/OrderListBuilder.php b/modules/order/src/OrderListBuilder.php index f0ce63f3..f23637cd 100644 --- a/modules/order/src/OrderListBuilder.php +++ b/modules/order/src/OrderListBuilder.php @@ -122,6 +122,13 @@ class OrderListBuilder extends EntityListBuilder { 'url' => $entity->toUrl('unlock-form'), ]; } + if ($entity->access('update') && $entity->hasLinkTemplate('resend-receipt')) { + $operations['resend_email'] = [ + 'title' => $this->t('Resend Email'), + 'weight' => 20, + 'url' => $entity->toUrl('resend-receipt'), + ]; + } return $operations; } diff --git a/modules/order/src/EventSubscriber/OrderReceiptSubscriber.php b/modules/order/src/OrderReceipt.php similarity index 78% copy from modules/order/src/EventSubscriber/OrderReceiptSubscriber.php copy to modules/order/src/OrderReceipt.php index 850e3363..e4928aa9 100644 --- a/modules/order/src/EventSubscriber/OrderReceiptSubscriber.php +++ b/modules/order/src/OrderReceipt.php @@ -1,21 +1,19 @@ getEntity(); + public function sendOrderReceipt(OrderInterface $order) { /** @var \Drupal\commerce_order\Entity\OrderTypeInterface $order_type */ $order_type = $this->orderTypeStorage->load($order->bundle()); if (!$order_type->shouldSendReceipt()) { @@ -107,7 +106,6 @@ class OrderReceiptSubscriber implements EventSubscriberInterface { $params = [ 'headers' => [ 'Content-Type' => 'text/html; charset=UTF-8;', - 'Content-Transfer-Encoding' => '8Bit', ], 'from' => $order->getStore()->getEmail(), 'subject' => $this->t('Order #@number confirmed', ['@number' => $order->getOrderNumber()]), @@ -137,15 +135,8 @@ class OrderReceiptSubscriber implements EventSubscriberInterface { $langcode = $this->languageManager->getDefaultLanguage()->getId(); } - $this->mailManager->mail('commerce_order', 'receipt', $to, $langcode, $params); - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events = ['commerce_order.place.post_transition' => ['sendOrderReceipt', -100]]; - return $events; + $message = $this->mailManager->mail('commerce_order', 'receipt', $to, $langcode, $params); + return(bool) $message['result']; } } diff --git a/modules/order/tests/src/Functional/ResendOrderReceiptTest.php b/modules/order/tests/src/Functional/ResendOrderReceiptTest.php new file mode 100755 index 00000000..7bb96fa6 --- /dev/null +++ b/modules/order/tests/src/Functional/ResendOrderReceiptTest.php @@ -0,0 +1,83 @@ +createEntity('commerce_order_item', [ + 'type' => 'default', + 'unit_price' => [ + 'number' => '999', + 'currency_code' => 'USD', + ], + ]); + /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ + $order = $this->createEntity('commerce_order', [ + 'type' => 'default', + 'mail' => $this->loggedInUser->getEmail(), + 'uid' => $this->loggedInUser->id(), + 'order_items' => [$order_item], + 'store_id' => $this->store, + ]); + + $order->setOrderNumber('2018/01'); + $order->save(); + + // Ensure that saving the order has not sent the receipt. + $mails = $this->getMails(); + $this->assertEquals(0, count($mails)); + + $this->assertNotEmpty($order->hasLinkTemplate('resend-receipt')); + + $this->drupalGet($order->toUrl('resend-receipt')); + $this->getSession()->getPage()->pressButton('Resend confirmation'); + + $mails = $this->getMails(); + $this->assertEquals(1, count($mails)); + + $the_email = reset($mails); + $this->assertEquals('text/html; charset=UTF-8;', $the_email['headers']['Content-Type']); + $this->assertEquals('8Bit', $the_email['headers']['Content-Transfer-Encoding']); + $this->assertEquals('Order #2018/01 confirmed', $the_email['subject']); + $this->assertEmpty(isset($the_email['headers']['Bcc'])); + + $this->assertSession()->pageTextContains('Email confirmation resent successfully.'); + } + +}