Skip to content

Commit 9b08fd4

Browse files
authored
added form for customer questions about products to product detail (#4661)
1 parent 349a653 commit 9b08fd4

17 files changed

Lines changed: 474 additions & 2 deletions

src/Component/Mailer/MailerHelper.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66

77
class MailerHelper
88
{
9+
public function escapeString(string $string): string
10+
{
11+
return htmlspecialchars($string, ENT_QUOTES);
12+
}
13+
914
public function escapeOptionalString(?string $string): string
1015
{
1116
if ($string === null) {
1217
return '-';
1318
}
1419

15-
return htmlspecialchars($string, ENT_QUOTES);
20+
return $this->escapeString($string);
1621
}
1722
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Migrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Override;
9+
use Shopsys\FrameworkBundle\Component\Translation\Translator;
10+
use Shopsys\FrameworkBundle\Model\ProductQuestion\Mail\ProductQuestionMail;
11+
use Shopsys\MigrationBundle\Component\Doctrine\Migrations\AbstractMigration;
12+
13+
final class Version20260618090000 extends AbstractMigration implements DomainAwareInterface
14+
{
15+
use MultidomainMigrationTrait;
16+
use MailTemplateMigrationTrait;
17+
18+
#[Override]
19+
public function up(Schema $schema): void
20+
{
21+
$this->insertMailTemplateIfNotExist(ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME);
22+
$this->insertMailTemplateIfNotExist(ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME);
23+
24+
foreach ($this->getAllDomainIds() as $domainId) {
25+
$domainLocale = $this->getDomainLocale($domainId);
26+
27+
$this->updateMailTemplate(
28+
ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME,
29+
t('New product question from {customerName}', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainLocale),
30+
$this->wrapMailTemplateBodyForGrapesJs(
31+
t(
32+
'<p>A customer has asked a question about a product.</p> <p><strong>Name:</strong> {customerName}<br> <strong>Email:</strong> {email}<br> <strong>Product:</strong> <a href="{productUrl}" tabindex="0">{productName}</a></p> <p><strong>Question:</strong><br>{question}</p>',
33+
[],
34+
Translator::DATA_FIXTURES_TRANSLATION_DOMAIN,
35+
$domainLocale,
36+
),
37+
),
38+
$domainId,
39+
);
40+
41+
$this->updateMailTemplate(
42+
ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME,
43+
t('Your question about {productName}', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainLocale),
44+
$this->wrapMailTemplateBodyForGrapesJs(
45+
t(
46+
'<p>Dear {customerName},</p> <p>Thank you for your question about <a href="{productUrl}" tabindex="0">{productName}</a>. We have received it and will get back to you by email as soon as possible.</p> <p><strong>Your question:</strong><br>{question}</p>',
47+
[],
48+
Translator::DATA_FIXTURES_TRANSLATION_DOMAIN,
49+
$domainLocale,
50+
),
51+
),
52+
$domainId,
53+
);
54+
}
55+
}
56+
57+
private function updateMailTemplate(string $mailTemplateName, string $subject, string $body, int $domainId): void
58+
{
59+
$this->sql(
60+
'UPDATE mail_templates SET subject = :subject, body = :body WHERE name = :mailTemplateName AND domain_id = :domainId',
61+
[
62+
'subject' => $subject,
63+
'body' => $body,
64+
'mailTemplateName' => $mailTemplateName,
65+
'domainId' => $domainId,
66+
],
67+
);
68+
}
69+
}

src/Model/Inquiry/Mail/InquiryMail.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ protected function getSubjectVariablesReplacements(Inquiry $inquiry): array
8686
self::VARIABLE_COMPANY_NAME => fn () => $this->mailerHelper->escapeOptionalString($inquiry->getCompanyName()),
8787
self::VARIABLE_COMPANY_NUMBER => fn () => $this->mailerHelper->escapeOptionalString($inquiry->getCompanyNumber()),
8888
self::VARIABLE_COMPANY_TAX_NUMBER => fn () => $this->mailerHelper->escapeOptionalString($inquiry->getCompanyTaxNumber()),
89-
self::VARIABLE_PRODUCT_NAME => fn () => $this->mailerHelper->escapeOptionalString($inquiry->getProduct()?->getName()),
89+
self::VARIABLE_PRODUCT_NAME => fn () => $inquiry->getProduct()?->getName(),
9090
self::VARIABLE_PRODUCT_CATALOG_NUMBER => fn () => htmlspecialchars($inquiry->getProductCatnum(), ENT_QUOTES),
9191
];
9292
}

src/Model/Mail/MailTemplateConfiguration.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use Shopsys\FrameworkBundle\Model\Order\Status\OrderStatusFacade;
2020
use Shopsys\FrameworkBundle\Model\PersonalData\Mail\PersonalDataAccessMail;
2121
use Shopsys\FrameworkBundle\Model\PersonalData\Mail\PersonalDataExportMail;
22+
use Shopsys\FrameworkBundle\Model\ProductQuestion\Mail\ProductQuestionMail;
23+
use Shopsys\FrameworkBundle\Model\ProductQuestion\Mail\ProductQuestionMailTemplateVariablesProvider;
2224
use Shopsys\FrameworkBundle\Model\Watchdog\Mail\WatchdogMail;
2325
use Shopsys\FrameworkBundle\Model\Watchdog\Mail\WatchdogMailTemplateVariablesProvider;
2426

@@ -42,6 +44,7 @@ public function __construct(
4244
protected readonly InquiryMailTemplateVariablesProvider $inquiryMailTemplateVariablesProvider,
4345
protected readonly WatchdogMailTemplateVariablesProvider $watchdogMailTemplateVariablesProvider,
4446
protected readonly ComplaintMail $complaintMail,
47+
protected readonly ProductQuestionMailTemplateVariablesProvider $productQuestionMailTemplateVariablesProvider,
4548
) {
4649
$this->registerStaticMailTemplates();
4750
$this->registerOrderStatusMailTemplates();
@@ -50,6 +53,7 @@ public function __construct(
5053
$this->registerCustomerActivationMailTemplate();
5154
$this->registerInquiryMailTemplates();
5255
$this->registerWatchdogMailTemplate();
56+
$this->registerProductQuestionMailTemplates();
5357
}
5458

5559
public function getMailTemplateVariablesBySlug(string $slug): MailTemplateVariables
@@ -348,4 +352,13 @@ protected function registerWatchdogMailTemplate(): void
348352
$watchdogMailTemplateVariables = $this->watchdogMailTemplateVariablesProvider->create();
349353
$this->addMailTemplateVariables(WatchdogMail::WATCHDOG_MAIL_TEMPLATE_NAME, $watchdogMailTemplateVariables);
350354
}
355+
356+
protected function registerProductQuestionMailTemplates(): void
357+
{
358+
$productQuestionMailTemplateVariables = $this->productQuestionMailTemplateVariablesProvider->create(ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME);
359+
$this->addMailTemplateVariables(ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME, $productQuestionMailTemplateVariables);
360+
361+
$productQuestionMailTemplateVariables = $this->productQuestionMailTemplateVariablesProvider->create(ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME);
362+
$this->addMailTemplateVariables(ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME, $productQuestionMailTemplateVariables);
363+
}
351364
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\Mail\MailTemplateSender;
6+
7+
use Override;
8+
use Shopsys\FrameworkBundle\Model\Mail\MailTemplate;
9+
use Shopsys\FrameworkBundle\Model\Product\ProductFacade;
10+
use Shopsys\FrameworkBundle\Model\ProductQuestion\Mail\ProductQuestionMail;
11+
use Shopsys\FrameworkBundle\Model\ProductQuestion\Mail\ProductQuestionMailFacade;
12+
use Shopsys\FrameworkBundle\Model\ProductQuestion\ProductQuestionDataFactory;
13+
14+
class ProductQuestionMailTemplateSender implements MailTemplateSenderInterface
15+
{
16+
public function __construct(
17+
protected readonly ProductQuestionMailFacade $productQuestionMailFacade,
18+
protected readonly ProductQuestionDataFactory $productQuestionDataFactory,
19+
protected readonly ProductFacade $productFacade,
20+
) {
21+
}
22+
23+
#[Override]
24+
public function getFormLabelForEntityIdentifier(): ?string
25+
{
26+
return t('Product ID');
27+
}
28+
29+
#[Override]
30+
public function supports(MailTemplate $mailTemplate): bool
31+
{
32+
return in_array($mailTemplate->getName(), [
33+
ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME,
34+
ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME,
35+
], true);
36+
}
37+
38+
#[Override]
39+
public function sendTemplate(MailTemplate $mailTemplate, string $mailTo, ?int $entityId): void
40+
{
41+
$productQuestionData = $this->productQuestionDataFactory->create($mailTemplate->getDomainId());
42+
$productQuestionData->customerName = t('Sample customer name');
43+
$productQuestionData->email = $mailTo;
44+
$productQuestionData->question = t('Sample product question.');
45+
$productQuestionData->product = $this->productFacade->getById($entityId);
46+
47+
$this->productQuestionMailFacade->sendMailTemplate($mailTemplate, $productQuestionData, $mailTo);
48+
}
49+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\ProductQuestion\Mail;
6+
7+
use Shopsys\FrameworkBundle\Component\Mailer\MailerHelper;
8+
use Shopsys\FrameworkBundle\Component\Router\DomainRouterFactory;
9+
use Shopsys\FrameworkBundle\Model\Mail\MailTemplate;
10+
use Shopsys\FrameworkBundle\Model\Mail\MessageData;
11+
use Shopsys\FrameworkBundle\Model\Mail\Setting\MailSettingFacade;
12+
use Shopsys\FrameworkBundle\Model\ProductQuestion\ProductQuestionData;
13+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
14+
15+
class ProductQuestionMail
16+
{
17+
public const string CUSTOMER_MAIL_TEMPLATE_NAME = 'product_question_customer';
18+
public const string ADMIN_MAIL_TEMPLATE_NAME = 'product_question_admin';
19+
20+
public const string VARIABLE_CUSTOMER_NAME = '{customerName}';
21+
public const string VARIABLE_EMAIL = '{email}';
22+
public const string VARIABLE_QUESTION = '{question}';
23+
public const string VARIABLE_PRODUCT_NAME = '{productName}';
24+
public const string VARIABLE_PRODUCT_URL = '{productUrl}';
25+
26+
public function __construct(
27+
protected readonly MailSettingFacade $mailSettingFacade,
28+
protected readonly DomainRouterFactory $domainRouterFactory,
29+
protected readonly MailerHelper $mailerHelper,
30+
) {
31+
}
32+
33+
public function createMessageForAdmin(
34+
MailTemplate $template,
35+
ProductQuestionData $productQuestionData,
36+
): MessageData {
37+
$mainAdminMail = $this->mailSettingFacade->getMainAdminMail($productQuestionData->domainId);
38+
39+
return new MessageData(
40+
$mainAdminMail,
41+
$template->getBccEmail(),
42+
$template->getBody(),
43+
$template->getSubject(),
44+
$mainAdminMail,
45+
$this->mailSettingFacade->getMainAdminMailName($productQuestionData->domainId),
46+
$this->getBodyVariablesReplacements($productQuestionData),
47+
$this->getSubjectVariablesReplacements($productQuestionData),
48+
replyTo: $productQuestionData->email,
49+
);
50+
}
51+
52+
public function createMessageForCustomer(
53+
MailTemplate $template,
54+
ProductQuestionData $productQuestionData,
55+
): MessageData {
56+
return new MessageData(
57+
$productQuestionData->email,
58+
$template->getBccEmail(),
59+
$template->getBody(),
60+
$template->getSubject(),
61+
$this->mailSettingFacade->getMainAdminMail($productQuestionData->domainId),
62+
$this->mailSettingFacade->getMainAdminMailName($productQuestionData->domainId),
63+
$this->getBodyVariablesReplacements($productQuestionData),
64+
$this->getSubjectVariablesReplacements($productQuestionData),
65+
);
66+
}
67+
68+
/**
69+
* @return array<string, \Closure>
70+
*/
71+
protected function getSubjectVariablesReplacements(ProductQuestionData $productQuestionData): array
72+
{
73+
return [
74+
self::VARIABLE_CUSTOMER_NAME => fn () => $this->mailerHelper->escapeString($productQuestionData->customerName),
75+
self::VARIABLE_EMAIL => fn () => $this->mailerHelper->escapeString($productQuestionData->email),
76+
self::VARIABLE_PRODUCT_NAME => fn () => $productQuestionData->product->getName(),
77+
];
78+
}
79+
80+
/**
81+
* @return array<string, \Closure>
82+
*/
83+
protected function getBodyVariablesReplacements(ProductQuestionData $productQuestionData): array
84+
{
85+
return [
86+
...$this->getSubjectVariablesReplacements($productQuestionData),
87+
self::VARIABLE_QUESTION => fn () => $this->mailerHelper->escapeString($productQuestionData->question),
88+
self::VARIABLE_PRODUCT_URL => fn () => $this->getProductUrl($productQuestionData),
89+
];
90+
}
91+
92+
protected function getProductUrl(ProductQuestionData $productQuestionData): string
93+
{
94+
return $this->domainRouterFactory->getRouter($productQuestionData->domainId)->generate(
95+
'front_product_detail',
96+
['id' => $productQuestionData->product->getId()],
97+
UrlGeneratorInterface::ABSOLUTE_URL,
98+
);
99+
}
100+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\ProductQuestion\Mail;
6+
7+
use Shopsys\FrameworkBundle\Component\UploadedFile\UploadedFileFacade;
8+
use Shopsys\FrameworkBundle\Model\Mail\Exception\MailTemplateNotFoundException;
9+
use Shopsys\FrameworkBundle\Model\Mail\Mailer;
10+
use Shopsys\FrameworkBundle\Model\Mail\MailTemplate;
11+
use Shopsys\FrameworkBundle\Model\Mail\MailTemplateFacade;
12+
use Shopsys\FrameworkBundle\Model\ProductQuestion\ProductQuestionData;
13+
14+
class ProductQuestionMailFacade
15+
{
16+
public function __construct(
17+
protected readonly Mailer $mailer,
18+
protected readonly MailTemplateFacade $mailTemplateFacade,
19+
protected readonly ProductQuestionMail $productQuestionMail,
20+
protected readonly UploadedFileFacade $uploadedFileFacade,
21+
) {
22+
}
23+
24+
public function sendMail(ProductQuestionData $productQuestionData): void
25+
{
26+
$adminMailTemplate = $this->mailTemplateFacade->getWrappedWithGrapesJsBody(ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME, $productQuestionData->domainId);
27+
$this->sendMailTemplate($adminMailTemplate, $productQuestionData);
28+
29+
$customerMailTemplate = $this->mailTemplateFacade->getWrappedWithGrapesJsBody(ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME, $productQuestionData->domainId);
30+
$this->sendMailTemplate($customerMailTemplate, $productQuestionData);
31+
}
32+
33+
public function sendMailTemplate(
34+
MailTemplate $mailTemplate,
35+
ProductQuestionData $productQuestionData,
36+
?string $forceSendTo = null,
37+
): void {
38+
$messageData = match ($mailTemplate->getName()) {
39+
ProductQuestionMail::ADMIN_MAIL_TEMPLATE_NAME => $this->productQuestionMail->createMessageForAdmin($mailTemplate, $productQuestionData),
40+
ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME => $this->productQuestionMail->createMessageForCustomer($mailTemplate, $productQuestionData),
41+
default => throw new MailTemplateNotFoundException($mailTemplate->getName()),
42+
};
43+
44+
if ($forceSendTo !== null) {
45+
$messageData->toEmail = $forceSendTo;
46+
}
47+
48+
$messageData->attachments = $this->uploadedFileFacade->getUploadedFilesByEntity($mailTemplate);
49+
$this->mailer->sendForDomain($messageData, $mailTemplate->getDomainId());
50+
}
51+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\FrameworkBundle\Model\ProductQuestion\Mail;
6+
7+
use Shopsys\FrameworkBundle\Model\Mail\MailTemplateVariables;
8+
9+
class ProductQuestionMailTemplateVariablesProvider
10+
{
11+
public function create(string $mailTemplateType): MailTemplateVariables
12+
{
13+
$mailTemplateName = $mailTemplateType === ProductQuestionMail::CUSTOMER_MAIL_TEMPLATE_NAME
14+
? t('Product question sent to customer')
15+
: t('Product question sent to administrator');
16+
17+
$mailTemplateVariables = new MailTemplateVariables($mailTemplateName);
18+
19+
$mailTemplateVariables->addVariable(
20+
ProductQuestionMail::VARIABLE_CUSTOMER_NAME,
21+
t('Customer name'),
22+
);
23+
24+
$mailTemplateVariables->addVariable(
25+
ProductQuestionMail::VARIABLE_EMAIL,
26+
t('Customer email address'),
27+
);
28+
29+
$mailTemplateVariables->addVariable(
30+
ProductQuestionMail::VARIABLE_QUESTION,
31+
t('Customer question'),
32+
MailTemplateVariables::CONTEXT_BODY,
33+
);
34+
35+
$mailTemplateVariables->addVariable(
36+
ProductQuestionMail::VARIABLE_PRODUCT_NAME,
37+
t('Product name'),
38+
);
39+
40+
$mailTemplateVariables->addVariable(
41+
ProductQuestionMail::VARIABLE_PRODUCT_URL,
42+
t('Product detail URL'),
43+
MailTemplateVariables::CONTEXT_BODY,
44+
);
45+
46+
return $mailTemplateVariables;
47+
}
48+
}

0 commit comments

Comments
 (0)