Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor contact module #8420

Merged
merged 19 commits into from
Feb 25, 2017
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
47a4681
Remove Zend_Validate calls
schmengler Feb 4, 2017
e1d47cc
Replace Zend_Validate_EmailAddress with 100% correct email validation
schmengler Feb 4, 2017
962c06d
Extract sendEmail method in contact form controller
schmengler Feb 4, 2017
fb902c6
Extract validatedParams method in contact form controller, use redire…
schmengler Feb 4, 2017
352dcef
Show meaningful error message if contact form validation fails
schmengler Feb 4, 2017
c8fbb70
Make validation and sending methods in contacts form controller prote…
schmengler Feb 4, 2017
36261ea
Add docblock comments
schmengler Feb 4, 2017
ec3fad1
Update unit test
schmengler Feb 4, 2017
0c78857
Fix code style violations
schmengler Feb 4, 2017
b4f68b0
Revert "Make validation and sending methods in contacts form controll…
schmengler Feb 16, 2017
5854390
Remove unnecessary dependency
schmengler Feb 16, 2017
854f16d
Moved down unnecessary dependencies from abstract base controller in …
schmengler Feb 16, 2017
22004d7
Move configuration access to config model / API interface
schmengler Feb 16, 2017
cdf8725
Merge remote-tracking branch 'origin/develop' into contact-refactor
schmengler Feb 16, 2017
28d939f
Extract mail sending from contacts controller, introduce mail service…
schmengler Feb 16, 2017
7a01613
Add docblock comments and empty lines
schmengler Feb 16, 2017
0261088
Add missing preference
schmengler Feb 16, 2017
a5c69d0
Move contact form interfaces from Api to Model namespace
schmengler Feb 19, 2017
f5ac9f7
Make method signature of Mail::send() more precise
schmengler Feb 19, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions app/code/Magento/Contact/Api/ConfigInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
/**
* Contact module base controller
*/
namespace Magento\Contact\Api;

/**
* Contact module configuration
*/
interface ConfigInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface is neither Service Interface (does not describe operations on data) nor Data Interface (it does not consist only of getters and setters for the fields). Also, it does not looks like it makes sense to expose it as Web API.

Let's move these two interfaces out of Api namespace to the Model. Please add @api annotation to the interface to indicate that this is backward compatible module's interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review. I did not intend to expose the interfaces to the web api, they were only meant as service contracts. I was not aware of the convention to distinguish between Api namespace and @api annotation, will change it as suggested

{
/**
* Recipient email config path
*/
const XML_PATH_EMAIL_RECIPIENT = 'contact/email/recipient_email';

/**
* Sender email config path
*/
const XML_PATH_EMAIL_SENDER = 'contact/email/sender_email_identity';

/**
* Email template config path
*/
const XML_PATH_EMAIL_TEMPLATE = 'contact/email/email_template';

/**
* Enabled config path
*/
const XML_PATH_ENABLED = 'contact/contact/enabled';

/**
* Check if contacts module is enabled
*
* @return bool
*/
public function isEnabled();

/**
* Return email template identifier
*
* @return string
*/
public function emailTemplate();

/**
* Return email sender address
*
* @return string
*/
public function emailSender();

/**
* Return email recipient address
*
* @return mixed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Service Contracts does not allow usage of "mixed" type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, should have been string anyway

*/
public function emailRecipient();
}
17 changes: 17 additions & 0 deletions app/code/Magento/Contact/Api/MailInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
/**
* Contact module base controller
*/
namespace Magento\Contact\Api;

interface MailInterface
{
/**
* Send email from contact form
*
* @param string $replyTo Reply-to email address
* @param array $variables Email template variables
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Service Contracts does not allow usage of "array" type, it should be more specific, i.e. "String[]"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but email template variables can be anything, from strings and integers to objects. How would you deal with it? mixed[] is as unspecific as array, so the only thing I can come up with is to hide the unspecifiy behind a DataObject.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now understand that these rules only apply to service contracts in the sense of interfaces that can be exposed as web API, so I assume that I can leave the signature as it is, after moving the interfaces out of /Api/

* @return void
*/
public function send($replyTo, $variables);
}
53 changes: 15 additions & 38 deletions app/code/Magento/Contact/Controller/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,51 @@
*/
namespace Magento\Contact\Controller;

use Magento\Framework\Exception\NotFoundException;
use Magento\Contact\Api\ConfigInterface;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\RequestInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\Exception\NotFoundException;

/**
* Contact index controller
* Contact module base controller
*/
abstract class Index extends \Magento\Framework\App\Action\Action
{
/**
* Recipient email config path
*/
const XML_PATH_EMAIL_RECIPIENT = 'contact/email/recipient_email';
const XML_PATH_EMAIL_RECIPIENT = ConfigInterface::XML_PATH_EMAIL_RECIPIENT;

/**
* Sender email config path
*/
const XML_PATH_EMAIL_SENDER = 'contact/email/sender_email_identity';
const XML_PATH_EMAIL_SENDER = ConfigInterface::XML_PATH_EMAIL_SENDER;

/**
* Email template config path
*/
const XML_PATH_EMAIL_TEMPLATE = 'contact/email/email_template';
const XML_PATH_EMAIL_TEMPLATE = ConfigInterface::XML_PATH_EMAIL_TEMPLATE;

/**
* Enabled config path
*/
const XML_PATH_ENABLED = 'contact/contact/enabled';

/**
* @var \Magento\Framework\Mail\Template\TransportBuilder
*/
protected $_transportBuilder;

/**
* @var \Magento\Framework\Translate\Inline\StateInterface
*/
protected $inlineTranslation;

/**
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $scopeConfig;
const XML_PATH_ENABLED = ConfigInterface::XML_PATH_ENABLED;

/**
* @var \Magento\Store\Model\StoreManagerInterface
* @var ConfigInterface
*/
protected $storeManager;
private $contactsConfig;

/**
* @param \Magento\Framework\App\Action\Context $context
* @param \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder
* @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
* @param Context $context
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
*/
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
\Magento\Framework\Translate\Inline\StateInterface $inlineTranslation,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Store\Model\StoreManagerInterface $storeManager
Context $context,
ConfigInterface $contactsConfig
) {
parent::__construct($context);
$this->_transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
$this->scopeConfig = $scopeConfig;
$this->storeManager = $storeManager;
$this->contactsConfig = $contactsConfig;
}

/**
Expand All @@ -84,7 +61,7 @@ public function __construct(
*/
public function dispatch(RequestInterface $request)
{
if (!$this->scopeConfig->isSetFlag(self::XML_PATH_ENABLED, ScopeInterface::SCOPE_STORE)) {
if (!$this->contactsConfig->isEnabled()) {
throw new NotFoundException(__('Page not found.'));
}
return parent::dispatch($request);
Expand Down
143 changes: 89 additions & 54 deletions app/code/Magento/Contact/Controller/Index/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
*/
namespace Magento\Contact\Controller\Index;

use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Contact\Api\ConfigInterface;
use Magento\Contact\Api\MailInterface;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\HTTP\PhpEnvironment\Request;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Translate\Inline\StateInterface;

class Post extends \Magento\Contact\Controller\Index
{
Expand All @@ -16,75 +24,60 @@ class Post extends \Magento\Contact\Controller\Index
*/
private $dataPersistor;

/**
* @var Context
*/
private $context;

/**
* @var MailInterface
*/
private $mail;

/**
* @param Context $context
* @param MailInterface $mail
* @param ConfigInterface $contactsConfig
* @param DataPersistorInterface $dataPersistor
*/
public function __construct(
Context $context,
MailInterface $mail,
ConfigInterface $contactsConfig,
DataPersistorInterface $dataPersistor
) {
parent::__construct($context, $contactsConfig);
$this->context = $context;
$this->mail = $mail;
$this->dataPersistor = $dataPersistor;
}

/**
* Post user question
*
* @return void
* @throws \Exception
* @return Redirect
*/
public function execute()
{
$post = $this->getRequest()->getPostValue();
if (!$post) {
$this->_redirect('*/*/');
return;
if (! $this->isPostRequest()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra space here after "!"

return $this->resultRedirectFactory->create()->setPath('*/*/');
}

$this->inlineTranslation->suspend();
try {
$postObject = new \Magento\Framework\DataObject();
$postObject->setData($post);

$error = false;

if (!\Zend_Validate::is(trim($post['name']), 'NotEmpty')) {
$error = true;
}
if (!\Zend_Validate::is(trim($post['comment']), 'NotEmpty')) {
$error = true;
}
if (!\Zend_Validate::is(trim($post['email']), 'EmailAddress')) {
$error = true;
}
if (\Zend_Validate::is(trim($post['hideit']), 'NotEmpty')) {
$error = true;
}
if ($error) {
throw new \Exception();
}

$storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE;
$transport = $this->_transportBuilder
->setTemplateIdentifier($this->scopeConfig->getValue(self::XML_PATH_EMAIL_TEMPLATE, $storeScope))
->setTemplateOptions(
[
'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
]
)
->setTemplateVars(['data' => $postObject])
->setFrom($this->scopeConfig->getValue(self::XML_PATH_EMAIL_SENDER, $storeScope))
->addTo($this->scopeConfig->getValue(self::XML_PATH_EMAIL_RECIPIENT, $storeScope))
->setReplyTo($post['email'])
->getTransport();

$transport->sendMessage();
$this->inlineTranslation->resume();
$this->sendEmail($this->validatedParams());
$this->messageManager->addSuccess(
__('Thanks for contacting us with your comments and questions. We\'ll respond to you very soon.')
);
$this->getDataPersistor()->clear('contact_us');
$this->_redirect('contact/index');
return;
} catch (LocalizedException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
$this->getDataPersistor()->set('contact_us', $this->getRequest()->getParams());
} catch (\Exception $e) {
$this->inlineTranslation->resume();
$this->messageManager->addError(
$this->messageManager->addErrorMessage(
__('We can\'t process your request right now. Sorry, that\'s all we know.')
);
$this->getDataPersistor()->set('contact_us', $post);
$this->_redirect('contact/index');
return;
$this->getDataPersistor()->set('contact_us', $this->getRequest()->getParams());
}
return $this->resultRedirectFactory->create()->setPath('contact/index');
}

/**
Expand All @@ -101,4 +94,46 @@ private function getDataPersistor()

return $this->dataPersistor;
}

/**
* @param array $post Post data from contact form
* @return void
*/
private function sendEmail($post)
{
$this->mail->send($post['email'], ['data' => new \Magento\Framework\DataObject($post)]);
}

/**
* @return bool
*/
private function isPostRequest()
{
/** @var Request $request */
$request = $this->getRequest();
return !empty($request->getPostValue());
}

/**
* @return array
* @throws \Exception
*/
private function validatedParams()
{
$request = $this->getRequest();
if (trim($request->getParam('name')) === '') {
throw new LocalizedException(__('Name is missing'));
}
if (trim($request->getParam('comment')) === '') {
throw new LocalizedException(__('Comment is missing'));
}
if (false === \strpos($request->getParam('email'), '@')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are losing quite a bit of functionality here vs Zend_Validate_EmailAddress. Maybe the better approach would be to switch to the ZF2 component https://github.com/zendframework/zend-validator/blob/master/src/EmailAddress.php#L492

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that on purpose because I don't believe in email address validation via regular expressions (relevant: https://hackernoon.com/the-100-correct-way-to-validate-email-addresses-7c4818f24643). If there's a decision to use the MX check, the ZF2 email validator would be useful again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great article - you got me convinced.

Copy link
Member

@joe-vortex joe-vortex Apr 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we not using php's native filter_var function? Surely this would be better as:

if (!filter_var($request->getParam('email'), FILTER_VALIDATE_EMAIL)) {
    throw new LocalizedException(__('Invalid email address'));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joe-vortex same reason that I dropped Zend_Validate_EmailAddress. The top comment on http://php.net/manual/en/function.filter-var.php already gives an example

throw new LocalizedException(__('Invalid email address'));
}
if (trim($request->getParam('hideit')) !== '') {
throw new \Exception();
}

return $request->getParams();
}
}
4 changes: 3 additions & 1 deletion app/code/Magento/Contact/Helper/Data.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Magento\Contact\Helper;

use Magento\Contact\Api\ConfigInterface;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Helper\View as CustomerViewHelper;
use Magento\Framework\App\Request\DataPersistorInterface;
Expand All @@ -16,7 +17,7 @@
*/
class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
const XML_PATH_ENABLED = 'contact/contact/enabled';
const XML_PATH_ENABLED = ConfigInterface::XML_PATH_ENABLED;

/**
* Customer session
Expand Down Expand Up @@ -59,6 +60,7 @@ public function __construct(
* Check if enabled
*
* @return string|null
* @deprecated use \Magento\Contact\Api\ConfigInterface::isEnabled() instead
*/
public function isEnabled()
{
Expand Down
Loading