Commit 0b23a18b authored by jsacksick's avatar jsacksick Committed by jsacksick

Issue #2918482 by andyg5000, mglaman, longwave, jsacksick, AmeDSL, abx:...

Issue #2918482 by andyg5000, mglaman, longwave, jsacksick, AmeDSL, abx: Provide a shipment confirmation email.
parent 4b2d3e15
......@@ -305,6 +305,14 @@ function commerce_shipping_theme() {
'commerce_shipment' => [
'render element' => 'elements',
],
'commerce_shipment_confirmation' => [
'variables' => [
'order_entity' => NULL,
'shipment_entity' => NULL,
'shipping_profile' => NULL,
'tracking_code' => NULL,
],
],
];
}
......
......@@ -83,3 +83,13 @@ services:
plugin.manager.commerce_package_type:
class: Drupal\commerce_shipping\PackageTypeManager
arguments: ['@module_handler', '@cache.discovery']
commerce_shipping.shipment_subscriber:
class: Drupal\commerce_shipping\EventSubscriber\ShipmentSubscriber
arguments: ['@entity_type.manager', '@commerce_shipping.shipment_confirmation_mail']
tags:
- { name: 'event_subscriber' }
commerce_shipping.shipment_confirmation_mail:
class: Drupal\commerce_shipping\Mail\ShipmentConfirmationMail
arguments: ['@entity_type.manager', '@commerce.mail_handler']
......@@ -89,6 +89,12 @@ commerce_shipping.commerce_shipment_type.*:
profileType:
type: string
label: 'The profile type'
sendConfirmation:
type: boolean
label: 'Send Confirmation'
confirmationBcc:
type: string
label: 'The confirmation BCC email'
commerce_shipping.commerce_package_type.*:
type: config_entity
......
......@@ -43,6 +43,7 @@ use Drupal\profile\Entity\ProfileInterface;
* "add" = "Drupal\commerce_shipping\Form\ShipmentForm",
* "edit" = "Drupal\commerce_shipping\Form\ShipmentForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* "resend-confirmation" = "Drupal\commerce_shipping\Form\ShipmentConfirmationResendForm",
* },
* "inline_form" = "Drupal\commerce_shipping\Form\ShipmentInlineForm",
* "views_data" = "Drupal\views\EntityViewsData",
......@@ -66,6 +67,7 @@ use Drupal\profile\Entity\ProfileInterface;
* "add-form" = "/admin/commerce/orders/{commerce_order}/shipments/add/{commerce_shipment_type}",
* "edit-form" = "/admin/commerce/orders/{commerce_order}/shipments/{commerce_shipment}/edit",
* "delete-form" = "/admin/commerce/orders/{commerce_order}/shipments/{commerce_shipment}/delete",
* "resend-confirmation-form" = "/admin/commerce/orders/{commerce_order}/shipments/{commerce_shipment}/resend-confirmation"
* },
* bundle_entity_type = "commerce_shipment_type",
* field_ui_base_route = "entity.commerce_shipment_type.edit_form",
......
......@@ -42,6 +42,8 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* "uuid",
* "profileType",
* "traits",
* "sendConfirmation",
* "confirmationBcc",
* },
* links = {
* "add-form" = "/admin/commerce/config/shipment-types/add",
......@@ -60,6 +62,20 @@ class ShipmentType extends CommerceBundleEntityBase implements ShipmentTypeInter
*/
protected $profileType = 'customer';
/**
* Shipping confirmation email enabled.
*
* @var bool
*/
protected $sendConfirmation;
/**
* Shipping confirmation BCC email address.
*
* @var string
*/
protected $confirmationBcc;
/**
* {@inheritdoc}
*/
......@@ -75,4 +91,34 @@ class ShipmentType extends CommerceBundleEntityBase implements ShipmentTypeInter
return $this;
}
/**
* {@inheritdoc}
*/
public function shouldSendConfirmation() {
return (bool) $this->sendConfirmation;
}
/**
* {@inheritdoc}
*/
public function setSendConfirmation($send_receipt) {
$this->sendConfirmation = (bool) $send_receipt;
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfirmationBcc() {
return $this->confirmationBcc;
}
/**
* {@inheritdoc}
*/
public function setConfirmationBcc($confirmation_bcc) {
$this->confirmationBcc = $confirmation_bcc;
return $this;
}
}
......@@ -27,4 +27,42 @@ interface ShipmentTypeInterface extends CommerceBundleEntityInterface {
*/
public function setProfileTypeId($profile_type_id);
/**
* Gets whether to email the customer when a shipment is shipped.
*
* @return bool
* TRUE if the confirmation email should be sent, FALSE otherwise.
*/
public function shouldSendConfirmation();
/**
* Sets whether to email the customer a shipment confirmation.
*
* @param bool $send_confirmation
* TRUE if the confirmation email should be sent, FALSE otherwise.
*
* @return $this
*/
public function setSendConfirmation($send_confirmation);
/**
* Gets the confirmation BCC email.
*
* If provided, this email will receive a copy of the confirmation email.
*
* @return string
* The confirmation BCC email.
*/
public function getConfirmationBcc();
/**
* Sets the confirmation BCC email.
*
* @param string $confirmation_bcc
* The confirmation BCC email.
*
* @return $this
*/
public function setConfirmationBcc($confirmation_bcc);
}
<?php
namespace Drupal\commerce_shipping\EventSubscriber;
use Drupal\commerce_shipping\Mail\ShipmentConfirmationMailInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\state_machine\Event\WorkflowTransitionEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ShipmentSubscriber implements EventSubscriberInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The shipment notification mail service.
*
* @var \Drupal\commerce_shipping\Mail\ShipmentConfirmationMailInterface
*/
protected $shipmentConfirmationMail;
/**
* Constructs a new ShipmentSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\commerce_shipping\Mail\ShipmentConfirmationMailInterface $shipment_confirmation_mail
* The shipment confirmation mail service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ShipmentConfirmationMailInterface $shipment_confirmation_mail) {
$this->entityTypeManager = $entity_type_manager;
$this->shipmentConfirmationMail = $shipment_confirmation_mail;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
'commerce_shipment.ship.post_transition' => ['onShip'],
];
}
/**
* Checks shipment mail settings and sends confirmation email to customer.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onShip(WorkflowTransitionEvent $event) {
/** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
$shipment = $event->getEntity();
/** @var \Drupal\commerce_shipping\Entity\ShipmentTypeInterface $shipment_type */
$shipment_type = $this->entityTypeManager->getStorage('commerce_shipment_type')->load($shipment->bundle());
$order = $shipment->getOrder();
assert($order !== NULL);
// Continue only if settings are configured to send confirmation.
if ($shipment_type->shouldSendConfirmation()) {
$this->shipmentConfirmationMail->send($shipment, $order->getEmail(), $shipment_type->getConfirmationBcc());
}
}
}
<?php
namespace Drupal\commerce_shipping\Form;
use Drupal\commerce_shipping\Mail\ShipmentConfirmationMailInterface;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a confirmation form for resending order shipment confirmations.
*/
class ShipmentConfirmationResendForm extends ContentEntityConfirmFormBase {
/**
* The shipment confirmation mail service.
*
* @var \Drupal\commerce_shipping\Mail\ShipmentConfirmationMailInterface
*/
protected $shipmentConfirmationMail;
/**
* Constructs a new ShipmentConfirmationResendForm object.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\commerce_shipping\Mail\ShipmentConfirmationMailInterface $shipment_confirmation_mail
* The shipment confirmation mail service.
*/
public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, ShipmentConfirmationMailInterface $shipment_confirmation_mail) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->shipmentConfirmationMail = $shipment_confirmation_mail;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.repository'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time'),
$container->get('commerce_shipping.shipment_confirmation_mail')
);
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to resend the shipment confirmation for %shipment for order %order?', [
'%shipment' => $this->entity->label(),
'%order' => $this->entity->getOrder()->label(),
]);
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Resend confirmation');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return $this->entity->toUrl('collection');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
$shipment = $this->entity;
$result = $this->shipmentConfirmationMail->send($shipment);
// Drupal's MailManager sets an error message itself, if the sending failed.
if ($result) {
$this->messenger()->addMessage($this->t('Shipment confirmation resent.'));
}
}
}
......@@ -82,6 +82,21 @@ class ShipmentTypeForm extends CommerceBundleEntityFormBase {
'#required' => TRUE,
'#disabled' => $shipments_exist,
];
$form['emails']['sendConfirmation'] = [
'#type' => 'checkbox',
'#title' => $this->t('Send customer an email confirmation when shipped'),
'#default_value' => $shipment_type->isNew() ? TRUE : $shipment_type->shouldSendConfirmation(),
];
$form['emails']['confirmationBcc'] = [
'#type' => 'textfield',
'#title' => $this->t('Send a copy of the shipment confirmation to this email:'),
'#default_value' => $shipment_type->isNew() ? '' : $shipment_type->getConfirmationBcc(),
'#states' => [
'visible' => [
':input[name="sendConfirmation"]' => ['checked' => TRUE],
],
],
];
$form = $this->buildTraitForm($form, $form_state);
return $this->protectBundleIdElement($form);
......
<?php
namespace Drupal\commerce_shipping\Mail;
use Drupal\commerce\MailHandlerInterface;
use Drupal\commerce_shipping\Entity\ShipmentInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
class ShipmentConfirmationMail implements ShipmentConfirmationMailInterface {
use StringTranslationTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The mail handler.
*
* @var \Drupal\commerce\MailHandlerInterface
*/
protected $mailHandler;
/**
* Constructs a new ShipmentConfirmationMail object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\commerce\MailHandlerInterface $mail_handler
* The mail handler.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, MailHandlerInterface $mail_handler) {
$this->entityTypeManager = $entity_type_manager;
$this->mailHandler = $mail_handler;
}
/**
* {@inheritdoc}
*/
public function send(ShipmentInterface $shipment, $to = NULL, $bcc = NULL) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $shipment->getOrder();
$to = isset($to) ? $to : $order->getEmail();
if (!$to) {
// The email should not be empty.
return FALSE;
}
$subject = $this->formatPlural(
$shipment->get('items')->count(),
'An item for order #@number shipped!',
'Items for your order #@number shipped!',
['@number' => $order->getOrderNumber()]
);
$profile_view_builder = $this->entityTypeManager->getViewBuilder('profile');
$shipment_view_builder = $this->entityTypeManager->getViewBuilder('commerce_shipment');
$body = [
'#theme' => 'commerce_shipment_confirmation',
'#order_entity' => $order,
'#shipment_entity' => $shipment,
'#shipping_profile' => $profile_view_builder->view($shipment->getShippingProfile()),
'#tracking_code' => $shipment_view_builder->viewField($shipment->get('tracking_code'), 'default'),
];
$params = [
'id' => 'shipment_confirmation',
'from' => $order->getStore()->getEmail(),
'bcc' => $bcc,
'order' => $order,
'shipment' => $shipment,
];
$customer = $order->getCustomer();
if ($customer->isAuthenticated()) {
$params['langcode'] = $customer->getPreferredLangcode();
}
return $this->mailHandler->sendMail($to, $subject, $body, $params);
}
}
<?php
namespace Drupal\commerce_shipping\Mail;
use Drupal\commerce_shipping\Entity\ShipmentInterface;
interface ShipmentConfirmationMailInterface {
/**
* Sends the shipment confirmation email.
*
* @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment
* The shipment.
* @param string $to
* The address the email will be sent to. Must comply with RFC 2822.
* Defaults to the order email.
* @param string $bcc
* The BCC address or addresses (separated by a comma).
*
* @return bool
* TRUE if the email was sent successfully, FALSE otherwise.
*/
public function send(ShipmentInterface $shipment, $to = NULL, $bcc = NULL);
}
......@@ -119,4 +119,21 @@ class ShipmentListBuilder extends EntityListBuilder {
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
protected function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if ($entity->getOrder()->access('resend_receipt')) {
$operations['resend_confirmation'] = [
'title' => $this->t('Resend confirmation'),
'weight' => 20,
'url' => $entity->toUrl('resend-confirmation-form'),
];
}
return $operations;
}
}
......@@ -5,12 +5,58 @@ namespace Drupal\commerce_shipping;
use Drupal\commerce_shipping\Controller\ShipmentController;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\entity\Routing\AdminHtmlRouteProvider;
use Symfony\Component\Routing\Route;
/**
* Provides routes for the Shipment entity.
*/
class ShipmentRouteProvider extends AdminHtmlRouteProvider {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$collection = parent::getRoutes($entity_type);
if ($resend_confirmation_form_route = $this->getResendConfirmationFormRoute($entity_type)) {
$collection->add("entity.commerce_shipment.resend_confirmation_form", $resend_confirmation_form_route);
}
return $collection;
}
/**
* Gets the resend-confirmation-form route.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available.
*/
protected function getResendConfirmationFormRoute(EntityTypeInterface $entity_type) {
$route = new Route($entity_type->getLinkTemplate('resend-confirmation-form'));
$route
->addDefaults([
'_entity_form' => 'commerce_shipment.resend-confirmation',
'_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
])
->setRequirement('_entity_access', 'commerce_order.resend_receipt')
->setRequirement('commerce_order', '\d+')
->setRequirement('commerce_shipment', '\d+')
->setOption('parameters', [
'commerce_order' => [
'type' => 'entity:commerce_order',
],
'commerce_shipment' => [
'type' => 'entity:commerce_shipment',
],
])
->setOption('_admin_route', TRUE);
return $route;
}
/**
* {@inheritdoc}
*/
......
{#
/**
* @file
* Template for the shipment confirmation.
*
* Available variables:
* - order_entity: The order entity.
* - shipment_entity: The shipment entity.
* - shipping_profile: The profile associated with a shipment.
* - tracking_code: The tracking code associated with the shipment.
*
* @ingroup themeable
*/
#}
{% set shipmentItemCount = shipment_entity.getItems|length %}
<table style="margin: 15px auto 0 auto; max-width: 768px; font-family: arial,sans-serif">
<tbody>
<tr>
<td>
<table style="margin-left: auto; margin-right: auto; max-width: 768px; text-align: center;">
<tbody>
<tr>
<td>
<a href="{{ url('<front>') }}" style="color: #0e69be; text-decoration: none; font-weight: bold; margin-top: 15px;">{{ order_entity.getStore.label }}</a>
</td>
</tr>
</tbody>
</table>
<table style="margin-left: auto; margin-right: auto; min-width: 450px; margin: 5px auto 0 auto; border: 1px solid #cccccc; border-radius: 5px; padding: 40px 30px 30px 30px;">
<tbody>
<tr>
<td style="text-align: center; font-weight: bold; padding-top:15px; padding-bottom: 15px; border-top: 1px solid #cccccc; border-bottom: 1px solid #cccccc">
{% trans %}
An item in your order #{{ order_entity.getOrderNumber }} has shipped!
{% plural shipmentItemCount %}
Items in your order #{{ order_entity.getOrderNumber }} have shipped!
{% endtrans %}
</td>
</tr>
<tr>
<td>
<table style="width: 100%; padding-top:15px; padding-bottom: 15px; text-align: left; border-bottom: 1px solid #cccccc">
<tbody>
<tr>
<td style="font-weight: bold; padding-bottom: 15px; text-align: left; vertical-align: top;">
{{ 'Shipped to:'|t }}
</td>
</tr>
<tr>
<td>
{% block shipping_profile %}
{{ shipping_profile }}
{% endblock %}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td>
{% block shipment_items %}
<table style="padding-top: 15px; padding-bottom:15px; width: 100%">
<tbody style="text-align: left;">
<tr>
<td colspan="2" style="font-weight: bold; padding-bottom: 15px; text-align: left; vertical-align: top;">
{% trans %}
Item in shipment
{% plural shipmentItemCount %}
Items in shipment
{% endtrans %}
</td>
</tr>
{% for shipment_item in shipment_entity.getItems() %}
<tr>
<td>
{{ shipment_item.quantity|number_format }} x
</td>
<td>
<span>{{ shipment_item.title }}</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
</td>
</tr>
{% if (tracking_code) %}
{% block tracking_info %}
<tr>
<td style="font-weight: bold; padding-top:15px; padding-bottom: 15px; text-align: left; vertical-align: top; border-top: 1px solid #cccccc">