diff --git a/modules/log/commerce_log.commerce_log_templates.yml b/modules/log/commerce_log.commerce_log_templates.yml index 469d9d5b..f07d5eb2 100644 --- a/modules/log/commerce_log.commerce_log_templates.yml +++ b/modules/log/commerce_log.commerce_log_templates.yml @@ -63,7 +63,15 @@ mail_order_receipt_failure: commerce_order_admin_comment: category: commerce_order label: 'Admin comment' - template: '

Admin comment:
{{ comment }}

' + template: '

Admin comment:
{{ comment|raw }}

' +commerce_order_from_customer_comment: + category: commerce_order + label: 'Comment from customer' + template: '

From customer:
{{ comment|raw }}

' +commerce_order_to_customer_comment: + category: commerce_order + label: 'Comment to customer' + template: '

{% if notify %}Emailed comment to customer{% else %}Comment to customer{% endif %}:
{{ comment|raw }}

' payment_added: category: commerce_payment label: 'Payment added' diff --git a/modules/log/commerce_log.module b/modules/log/commerce_log.module index 43280b8f..ceaa5403 100644 --- a/modules/log/commerce_log.module +++ b/modules/log/commerce_log.module @@ -41,3 +41,45 @@ function commerce_log_add_form_submit($form, FormStateInterface $form_state) { $order = $order_storage->load($form_state->getValue('order_id')); $log_storage->generate($order, 'order_created_admin')->save(); } + +/** + * Implements hook_theme(). + */ +function commerce_log_theme($existing, $type, $theme, $path) { + return [ + 'commerce_log_order_comments' => [ + 'variables' => [ + 'order_entity' => NULL, + 'billing_information' => NULL, + 'shipping_information' => NULL, + 'payment_method' => NULL, + 'totals' => NULL, + 'order_comments' => [], + ], + ], + ]; +} + +/** + * Implements hook_theme_suggestions_HOOK(). + */ +function commerce_log_theme_suggestions_commerce_log_order_comments(array $variables) { + $suggestions = []; + if (!empty($variables['order_entity'])) { + $suggestions[] = $variables['theme_hook_original'] . '__' . $variables['order_entity']->bundle(); + } + return $suggestions; +} + +/** + * Implements hook_mail(). + */ +function commerce_log_mail($key, &$message, $params) { + if (!empty($params['headers'])) { + $message['headers'] = array_merge($message['headers'], $params['headers']); + } + + $message['from'] = $params['from']; + $message['subject'] = $params['subject']; + $message['body'][] = $params['body']; +} diff --git a/modules/log/src/CommerceLogServiceProvider.php b/modules/log/src/CommerceLogServiceProvider.php index 120fe59d..288b2304 100644 --- a/modules/log/src/CommerceLogServiceProvider.php +++ b/modules/log/src/CommerceLogServiceProvider.php @@ -32,7 +32,9 @@ class CommerceLogServiceProvider extends ServiceProviderBase { if (isset($modules['commerce_order'])) { $container->register('commerce_log.order_subscriber', 'Drupal\commerce_log\EventSubscriber\OrderEventSubscriber') ->addTag('event_subscriber') - ->addArgument(new Reference('entity_type.manager')); + ->addArgument(new Reference('entity_type.manager')) + ->addArgument(new Reference('plugin.manager.mail')) + ->addArgument(new Reference('renderer')); $container->register('commerce_log.order_mail_subscriber', 'Drupal\commerce_log\EventSubscriber\OrderMailEventSubscriber') ->addTag('event_subscriber') diff --git a/modules/log/src/Event/OrderCommentEvent.php b/modules/log/src/Event/OrderCommentEvent.php new file mode 100644 index 00000000..4bf4f7cd --- /dev/null +++ b/modules/log/src/Event/OrderCommentEvent.php @@ -0,0 +1,122 @@ +order = $order; + $this->type = $type; + $this->comment = $comment; + $this->notify = $notify; + } + + /** + * Gets the order. + * + * @return \Drupal\commerce_order\Entity\OrderInterface + * The order. + */ + public function getOrder(): OrderInterface { + return $this->order; + } + + /** + * Gets the order comment. + * + * @return string + * The comment. + */ + public function getComment(): string { + return $this->comment; + } + + /** + * Returns the choice to email customer or not. + * + * @return bool + * The choice to email customer or not. + */ + public function notifyCustomer(): bool { + return $this->notify; + } + + /** + * Returns EventType ID. + * + * @return string + * The EventType ID. + */ + public function getEventType(): string { + return $this->type; + } + +} diff --git a/modules/log/src/EventSubscriber/OrderEventSubscriber.php b/modules/log/src/EventSubscriber/OrderEventSubscriber.php index 59d9ed18..d8908d29 100644 --- a/modules/log/src/EventSubscriber/OrderEventSubscriber.php +++ b/modules/log/src/EventSubscriber/OrderEventSubscriber.php @@ -2,13 +2,20 @@ namespace Drupal\commerce_log\EventSubscriber; +use Drupal\commerce_log\Event\OrderCommentEvent; use Drupal\commerce_order\Event\OrderAssignEvent; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Mail\MailManagerInterface; +use Drupal\Core\Render\Renderer; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\state_machine\Event\WorkflowTransitionEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class OrderEventSubscriber implements EventSubscriberInterface { + use StringTranslationTrait; + /** * The log storage. * @@ -16,23 +23,54 @@ class OrderEventSubscriber implements EventSubscriberInterface { */ protected $logStorage; + /** + * The mail manager. + * + * @var \Drupal\Core\Mail\MailManagerInterface + */ + protected $mailManager; + + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * The profile view builder. + * + * @var \Drupal\profile\ProfileViewBuilder + */ + protected $profileViewBuilder; + /** * Constructs a new OrderEventSubscriber object. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. + * @param \Drupal\Core\Mail\MailManagerInterface $mail_manager + * The mail manager. + * @param \Drupal\Core\Render\Renderer $renderer + * The renderer. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, MailManagerInterface $mail_manager, Renderer $renderer) { $this->logStorage = $entity_type_manager->getStorage('commerce_log'); + $this->mailManager = $mail_manager; + $this->renderer = $renderer; + $this->profileViewBuilder = $entity_type_manager->getViewBuilder('profile'); } /** * {@inheritdoc} */ - public static function getSubscribedEvents() { + public static function getSubscribedEvents(): array { return [ 'commerce_order.order.assign' => ['onOrderAssign', -100], 'commerce_order.post_transition' => ['onOrderPostTransition'], + 'commerce_order.comment.admin' => ['onAdminComment', -100], + 'commerce_order.comment.from_customer' => ['onFromCustomerComment', -100], + 'commerce_order.comment.to_customer' => ['onToCustomerComment', -100], ]; } @@ -41,8 +79,10 @@ class OrderEventSubscriber implements EventSubscriberInterface { * * @param \Drupal\commerce_order\Event\OrderAssignEvent $event * The order assign event. + * + * @throws \Drupal\Core\Entity\EntityStorageException */ - public function onOrderAssign(OrderAssignEvent $event) { + public function onOrderAssign(OrderAssignEvent $event): void { $order = $event->getOrder(); $this->logStorage->generate($order, 'order_assigned', [ 'user' => $event->getCustomer()->getDisplayName(), @@ -54,8 +94,10 @@ class OrderEventSubscriber implements EventSubscriberInterface { * * @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event * The transition event. + * + * @throws \Drupal\Core\Entity\EntityStorageException */ - public function onOrderPostTransition(WorkflowTransitionEvent $event) { + public function onOrderPostTransition(WorkflowTransitionEvent $event): void { $transition = $event->getTransition(); /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $event->getEntity(); @@ -69,4 +111,98 @@ class OrderEventSubscriber implements EventSubscriberInterface { ])->save(); } + /** + * Adds admin facing comment to an order. + * + * @param \Drupal\commerce_log\Event\OrderCommentEvent $event + * The event. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + public function onAdminComment(OrderCommentEvent $event): void { + $this->logStorage->generate($event->getOrder(), 'commerce_order_admin_comment', [ + 'comment' => $event->getComment(), + 'notify' => $event->notifyCustomer(), + ])->save(); + } + + /** + * Adds customer facing order checkout comment. + * + * @param \Drupal\commerce_log\Event\OrderCommentEvent $event + * The event. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + public function onFromCustomerComment(OrderCommentEvent $event): void { + $this->logStorage->generate($event->getOrder(), 'commerce_order_from_customer_comment', [ + 'comment' => $event->getComment(), + 'notify' => $event->notifyCustomer(), + ])->save(); + + // Because we display customer comments on their order view, we need to + // flush the caches, so it updates. + $order = $event->getOrder(); + Cache::invalidateTags($order->getCacheTags()); + } + + /** + * Adds customer facing order comment and sends email notification if checked. + * + * @param \Drupal\commerce_log\Event\OrderCommentEvent $event + * The event. + * + * @throws \Exception + */ + public function onToCustomerComment(OrderCommentEvent $event): void { + $this->logStorage->generate($event->getOrder(), 'commerce_order_to_customer_comment', [ + 'comment' => $event->getComment(), + 'notify' => $event->notifyCustomer(), + ])->save(); + + // Because we display customer comments on their order view, we need to + // flush the caches, so it updates. + $order = $event->getOrder(); + Cache::invalidateTags($order->getCacheTags()); + + // If notify customer is selected, send email of order comment. + if ($event->notifyCustomer() && $order->getEmail()) { + $build = [ + '#theme' => 'commerce_log_order_comments', + '#order_entity' => $order, + '#totals' => \Drupal::service('commerce_order.order_total_summary')->buildTotals($order), + ]; + + // Get billing profile. + if ($order->getBillingProfile()) { + $build['#billing_information'] = $this->profileViewBuilder->view($order->getBillingProfile()); + } + + // Get shipping profile. + if (\Drupal::service('module_handler')->moduleExists('commerce_shipping')) { + $summary = \Drupal::service('commerce_shipping.order_shipment_summary')->build($order); + if (!empty($summary)) { + $build['#shipping_information'] = $summary; + } + } + + // Get order comments. + $build['#order_comments'] = \Drupal::service('commerce_order.order_receipt_mail')->getOrderComments($order); + + $params = [ + 'headers' => [ + 'Content-Type' => 'text/html; charset=UTF-8;', + 'Content-Transfer-Encoding' => '8Bit', + ], + 'from' => $order->getStore()->getEmail(), + 'subject' => $this->t('New comment on order #@number', ['@number' => $order->getOrderNumber()]), + 'order' => $order, + 'body' => $this->renderer->render($build), + ]; + + $customer = $order->getCustomer(); + $this->mailManager->mail('commerce_log', 'comment', $order->getEmail(), $customer->getPreferredLangcode(), $params); + } + } + } diff --git a/modules/log/src/Form/LogCommentForm.php b/modules/log/src/Form/LogCommentForm.php index ee8ba96f..9b0967e3 100644 --- a/modules/log/src/Form/LogCommentForm.php +++ b/modules/log/src/Form/LogCommentForm.php @@ -2,8 +2,10 @@ namespace Drupal\commerce_log\Form; +use Drupal\commerce_log\Event\OrderCommentEvent; use Drupal\commerce_log\LogStorageInterface; use Drupal\commerce_log\LogTemplateManagerInterface; +use Drupal\commerce_order\Entity\OrderInterface; use Drupal\Component\Utility\Html; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityStorageInterface; @@ -11,6 +13,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class LogCommentForm extends FormBase { @@ -28,6 +31,13 @@ class LogCommentForm extends FormBase { */ protected $logTemplateManager; + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + /** * Constructs a new LogCommentForm object. * @@ -35,10 +45,13 @@ class LogCommentForm extends FormBase { * The entity type manager. * @param \Drupal\commerce_log\LogTemplateManagerInterface $log_template_manager * The log template manager. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher + * The event dispatcher. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, LogTemplateManagerInterface $log_template_manager) { + public function __construct(EntityTypeManagerInterface $entity_type_manager, LogTemplateManagerInterface $log_template_manager, EventDispatcherInterface $event_dispatcher) { $this->entityTypeManager = $entity_type_manager; $this->logTemplateManager = $log_template_manager; + $this->eventDispatcher = $event_dispatcher; } /** @@ -47,7 +60,8 @@ class LogCommentForm extends FormBase { public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), - $container->get('plugin.manager.commerce_log_template') + $container->get('plugin.manager.commerce_log_template'), + $container->get('event_dispatcher') ); } @@ -94,6 +108,29 @@ class LogCommentForm extends FormBase { '#title_display' => 'invisible', '#required' => TRUE, ]; + + if ($source_entity_type == 'commerce_order') { + $form['log_comment']['type'] = [ + '#type' => 'radios', + '#title' => $this->t('Comment type'), + '#default_value' => OrderCommentEvent::ORDER_ADMIN_COMMENT, + '#options' => [ + OrderCommentEvent::ORDER_ADMIN_COMMENT => $this->t('Admin-only comment'), + OrderCommentEvent::ORDER_TO_CUSTOMER_COMMENT => $this->t('Customer-facing comment'), + ], + ]; + $form['log_comment']['notify'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Notify customer by email'), + '#default_value' => FALSE, + '#states' => [ + 'enabled' => [ + ':input[name="type"]' => ['value' => OrderCommentEvent::ORDER_TO_CUSTOMER_COMMENT], + ], + ], + ]; + } + $form['log_comment']['actions']['#type'] = 'actions'; $form['log_comment']['actions']['submit'] = [ '#type' => 'submit', @@ -114,7 +151,14 @@ class LogCommentForm extends FormBase { $entity = $storage->load($form_state->getValue('source_entity_id')); assert($entity instanceof ContentEntityInterface); $comment = nl2br(Html::escape($form_state->getValue('comment'))); - $log_storage->generate($entity, $form_state->getValue('log_template_id'), ['comment' => $comment])->save(); + if ($entity instanceof OrderInterface) { + // Dispatch the comment event. + $event = new OrderCommentEvent($entity, $form_state->getValue('type'), $comment, $form_state->getValue('notify')); + $this->eventDispatcher->dispatch($event, $event->getEventType()); + } + else { + $log_storage->generate($entity, $form_state->getValue('log_template_id'), ['comment' => $comment])->save(); + } $this->messenger()->addStatus($this->t('Comment saved')); } diff --git a/modules/log/src/LogCommentPermissions.php b/modules/log/src/LogCommentPermissions.php index e7802e70..5db7b79a 100644 --- a/modules/log/src/LogCommentPermissions.php +++ b/modules/log/src/LogCommentPermissions.php @@ -49,7 +49,7 @@ class LogCommentPermissions implements ContainerInjectionInterface { } /** - * Builds a list of permissions for entity types that support comments.. + * Builds a list of permissions for entity types that support comments. * * @return array * The permissions. @@ -66,6 +66,12 @@ class LogCommentPermissions implements ContainerInjectionInterface { 'restrict access' => TRUE, 'provider' => $entity_type->getProvider(), ]; + $permissions["add commerce_log {$entity_type_id} customer comment"] = [ + 'title' => $this->t('Add customer comments to own @label', ['@label' => $entity_type->getSingularLabel()]), + 'description' => $this->t('Provides the ability to add customer comments to @label.', ['@label' => $entity_type->getPluralLabel()]), + 'restrict access' => TRUE, + 'provider' => $entity_type->getProvider(), + ]; } } return $permissions; diff --git a/modules/log/src/Plugin/Commerce/CheckoutPane/CustomerCommentsPane.php b/modules/log/src/Plugin/Commerce/CheckoutPane/CustomerCommentsPane.php new file mode 100644 index 00000000..09fd51cf --- /dev/null +++ b/modules/log/src/Plugin/Commerce/CheckoutPane/CustomerCommentsPane.php @@ -0,0 +1,119 @@ +eventDispatcher = $event_dispatcher; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $checkout_flow, + $container->get('entity_type.manager'), + $container->get('event_dispatcher') + ); + } + + /** + * {@inheritdoc} + */ + public function isVisible() { + return \Drupal::currentUser()->hasPermission('add commerce_log commerce_order customer comment'); + } + + /** + * {@inheritdoc} + */ + public function buildPaneSummary() { + $summary = parent::buildPaneSummary(); + $logStorage = $this->entityTypeManager->getStorage('commerce_log'); + /** @var \Drupal\commerce_log\Entity\Log $logEntry */ + foreach ($logStorage->loadMultipleByEntity($this->order) as $logEntry) { + if ($logEntry->getTemplateId() == 'commerce_order_from_customer_comment' && !empty($logEntry->getParams()['comment'])) { + $summary[] = ['#plain_text' => $logEntry->getParams()['comment']]; + } + } + + return $summary; + } + + /** + * {@inheritdoc} + */ + public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) { + $pane_form['comments'] = [ + '#type' => 'textarea', + '#title' => $this->t('Comments'), + '#title_display' => 'invisible', + '#default_value' => '', + ]; + + return $pane_form; + } + + /** + * {@inheritdoc} + */ + public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) { + parent::submitPaneForm($pane_form, $form_state, $complete_form); + // Dispatch the comment event. + if (!empty($form_state->getValue('customer_comments')['comments'])) { + $comment = nl2br(Html::escape($form_state->getValue('customer_comments')['comments'])); + $event = new OrderCommentEvent($this->order, OrderCommentEvent::ORDER_FROM_CUSTOMER_COMMENT, $comment, FALSE); + $this->eventDispatcher->dispatch($event, $event->getEventType()); + } + } + +} diff --git a/modules/log/templates/commerce-log-order-comments.html.twig b/modules/log/templates/commerce-log-order-comments.html.twig new file mode 100644 index 00000000..b8e25e6a --- /dev/null +++ b/modules/log/templates/commerce-log-order-comments.html.twig @@ -0,0 +1 @@ +{% extends 'commerce-order-receipt.html.twig' %} diff --git a/modules/log/tests/src/Functional/OrderAdminTest.php b/modules/log/tests/src/Functional/OrderAdminTest.php index 330a7c29..705ccbdb 100644 --- a/modules/log/tests/src/Functional/OrderAdminTest.php +++ b/modules/log/tests/src/Functional/OrderAdminTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\commerce_log\Functional; use Drupal\commerce_order\Entity\Order; -use Drupal\Component\Utility\Html; use Drupal\Tests\commerce_order\Functional\OrderBrowserTestBase; /** @@ -65,8 +64,7 @@ class OrderAdminTest extends OrderBrowserTestBase { $this->getSession()->getPage()->fillField('Comment', $test_filtered_comment); $this->getSession()->getPage()->pressButton('Add comment'); - $this->assertSession()->pageTextNotContains($test_filtered_comment); - $this->assertSession()->pageTextContains(Html::escape($test_filtered_comment)); + $this->assertSession()->pageTextContains($test_filtered_comment); } /** diff --git a/modules/order/commerce_order.module b/modules/order/commerce_order.module index af2620fa..830a4300 100644 --- a/modules/order/commerce_order.module +++ b/modules/order/commerce_order.module @@ -46,6 +46,7 @@ function commerce_order_theme($existing, $type, $theme, $path) { 'shipping_information' => NULL, 'payment_method' => NULL, 'totals' => NULL, + 'order_comments' => [], ], ], 'commerce_order_receipt__entity_print' => [ @@ -181,8 +182,8 @@ function template_preprocess_commerce_order(array &$variables) { } if ($billing_profile = $order->getBillingProfile()) { - $profile_view_bulder = \Drupal::entityTypeManager()->getViewBuilder('profile'); - $variables['order']['billing_information'] = $profile_view_bulder->view($billing_profile, $view_mode); + $profile_view_builder = \Drupal::entityTypeManager()->getViewBuilder('profile'); + $variables['order']['billing_information'] = $profile_view_builder->view($billing_profile, $view_mode); } $route_name = \Drupal::routeMatch()->getRouteName(); @@ -201,6 +202,9 @@ function template_preprocess_commerce_order(array &$variables) { $messenger->addStatus(t('This order is locked and cannot be edited or deleted. You can unlock it here.', [':link' => $order_unlock_link])); $messenger->addStatus(t('Orders are typically locked during the payment step in checkout to ensure prices on it do not change during a payment attempt. If the customer is currently paying for this order on a hosted payment page, editing this order could result in a mismatch between the order total and payment amount.')); } + + // Get order comments. + $variables['order_comments'] = \Drupal::service('commerce_order.order_receipt_mail')->getOrderComments($order); } /** diff --git a/modules/order/src/Mail/OrderReceiptMail.php b/modules/order/src/Mail/OrderReceiptMail.php index 67affa1e..f3a2ad10 100644 --- a/modules/order/src/Mail/OrderReceiptMail.php +++ b/modules/order/src/Mail/OrderReceiptMail.php @@ -3,6 +3,7 @@ namespace Drupal\commerce_order\Mail; use Drupal\commerce\MailHandlerInterface; +use Drupal\commerce_log\Event\OrderCommentEvent; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_order\OrderTotalSummaryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -30,7 +31,7 @@ class OrderReceiptMail implements OrderReceiptMailInterface { /** * {@inheritdoc} */ - public function send(OrderInterface $order, $to = NULL, $bcc = NULL, bool $resend = FALSE) { + public function send(OrderInterface $order, $to = NULL, $bcc = NULL, bool $resend = FALSE): bool { $to = $to ?? $order->getEmail(); if (!$to) { // The email should not be empty. @@ -61,6 +62,9 @@ class OrderReceiptMail implements OrderReceiptMailInterface { $body['#billing_information'] = $profile_view_builder->view($billing_profile); } + // Get order comments. + $body['#order_comments'] = $this->getOrderComments($order); + $params = [ 'id' => 'order_receipt', 'from' => $order->getStore()->getEmailFromHeader(), @@ -76,4 +80,35 @@ class OrderReceiptMail implements OrderReceiptMailInterface { return $this->mailHandler->sendMail($to, $subject, $body, $params); } + /** + * Get customer viewable order comments. + * + * @param \Drupal\commerce_order\Entity\OrderInterface $order + * The order entity. + * + * @return array + * The order comments. + */ + public function getOrderComments(OrderInterface $order): array { + $orderComments = []; + if (\Drupal::moduleHandler()->moduleExists('commerce_log')) { + $logStorage = \Drupal::entityTypeManager()->getStorage('commerce_log'); + /** @var \Drupal\commerce_log\Entity\LogInterface $logEntry */ + foreach (array_reverse($logStorage->loadMultipleByEntity($order)) as $logEntry) { + if ($logEntry->getTemplateId() === 'commerce_order_to_customer_comment') { + $orderComments[$logEntry->id()]['type'] = OrderCommentEvent::ORDER_TO_CUSTOMER_COMMENT; + $orderComments[$logEntry->id()]['date'] = $logEntry->getCreatedTime(); + $orderComments[$logEntry->id()]['comment'] = $logEntry->getParams()['comment']; + } + elseif ($logEntry->getTemplateId() === 'commerce_order_from_customer_comment') { + $orderComments[$logEntry->id()]['type'] = OrderCommentEvent::ORDER_FROM_CUSTOMER_COMMENT; + $orderComments[$logEntry->id()]['date'] = $logEntry->getCreatedTime(); + $orderComments[$logEntry->id()]['comment'] = $logEntry->getParams()['comment']; + } + } + } + + return $orderComments; + } + } diff --git a/modules/order/templates/commerce-order--user.html.twig b/modules/order/templates/commerce-order--user.html.twig index 7ef237c3..0b545c0b 100644 --- a/modules/order/templates/commerce-order--user.html.twig +++ b/modules/order/templates/commerce-order--user.html.twig @@ -13,6 +13,7 @@ * {{ order|without('order_number') }} * @endcode * - order_entity: The order entity. + * - order_comments: Customer viewable comments. * * @ingroup themeable */ @@ -33,6 +34,34 @@ {% endif %} +
+ {% if order_comments %} +
{{ 'Comments'|t }}
+
+ + + + + + + + + {% for comment in order_comments %} + + {% if comment.type == 'commerce_order.comment.to_customer' %} + + + {% elseif comment.type == 'commerce_order.comment.from_customer' %} + + + {% endif %} + + {% endfor %} + +
DateMessage
{{ comment.date|date("D, m/d/Y - H:i")}}Comment to customer:
{{ comment.comment|raw }}
{{ comment.date|date("D, m/d/Y - H:i") }}Comment from customer:
{{ comment.comment|raw }}
+
+ {% endif %} +
{{ order.completed }} {{ order.placed }} diff --git a/modules/order/templates/commerce-order-receipt.html.twig b/modules/order/templates/commerce-order-receipt.html.twig index 1268d853..65ee10f0 100644 --- a/modules/order/templates/commerce-order-receipt.html.twig +++ b/modules/order/templates/commerce-order-receipt.html.twig @@ -16,6 +16,7 @@ * - total: The adjustment total price. * - weight: The adjustment weight, taken from the adjustment type. * - total: The order total price. + * - order_comments: Customer viewable comments. * * @ingroup themeable */ @@ -133,6 +134,44 @@

+ {% if order_comments %} + + +
+ + + + + + +
{{ 'Comments'|t }}
+
+ + + + + + + + + {% for comment in order_comments %} + + {% if comment.type == 'commerce_order.comment.to_customer' %} + + + {% elseif comment.type == 'commerce_order.comment.from_customer' %} + + + {% endif %} + + {% endfor %} + +
DateMessage
{{ comment.date|date("D, m/d/Y - H:i") }}Comment to customer:
{{ comment.comment|raw }}
{{ comment.date|date("D, m/d/Y - H:i") }}Comment from customer:
{{ comment.comment|raw }}
+
+
+ + + {% endif %} {% block additional_information %} diff --git a/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php b/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php index 9a1e3b3a..89aadd16 100644 --- a/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php +++ b/modules/order/tests/src/FunctionalJavascript/OrderAdminTest.php @@ -424,7 +424,7 @@ class OrderAdminTest extends OrderWebDriverTestBase { ]); // First test that the current admin user can see the order. - $this->drupalGet($order->toUrl()->toString()); + $this->drupalGet($order->toUrl()); $this->assertSession()->pageTextContains($this->loggedInUser->getEmail()); // Confirm that the order item table is showing the empty text. @@ -462,14 +462,14 @@ class OrderAdminTest extends OrderWebDriverTestBase { $order->setItems([$order_item]); $order->save(); - $this->drupalGet($order->toUrl()->toString()); + $this->drupalGet($order->toUrl()); $this->assertSession()->pageTextNotContains('There are no order items yet.'); $this->assertSession()->pageTextContains('$999.00'); $this->assertSession()->pageTextContains('Subtotal'); // Logout and check that anonymous users cannot see the order admin screen. $this->drupalLogout(); - $this->drupalGet($order->toUrl()->toString()); + $this->drupalGet($order->toUrl()); $this->assertSession()->pageTextContains('Access denied'); } diff --git a/modules/payment/commerce_payment.module b/modules/payment/commerce_payment.module index eaf71cd5..00c1736b 100644 --- a/modules/payment/commerce_payment.module +++ b/modules/payment/commerce_payment.module @@ -211,3 +211,10 @@ function commerce_payment_validate_checkout_flow(array $form, FormStateInterface $form_state->setError($form, t('Payment information and Payment process panes need to be on separate steps.')); } } + +/** + * Implements hook_preprocess_HOOK(). + */ +function commerce_payment_preprocess_commerce_log_order_comments(&$variables) { + commerce_payment_preprocess_commerce_order($variables); +} diff --git a/modules/payment/tests/src/Functional/DefaultPaymentAdminTest.php b/modules/payment/tests/src/Functional/DefaultPaymentAdminTest.php index 99f9b2c6..53b1514d 100644 --- a/modules/payment/tests/src/Functional/DefaultPaymentAdminTest.php +++ b/modules/payment/tests/src/Functional/DefaultPaymentAdminTest.php @@ -132,7 +132,7 @@ class DefaultPaymentAdminTest extends CommerceBrowserTestBase { [ 'absolute' => TRUE, ], - )->toString(); + )->getInternalPath(); } /**