Análisis del módulo Contact de Magento 2
La mejor forma de aprender Magento es analizando y comprendiendo sus módulos. En este artículo, analizaremos el módulo de los contactos.
# Módulo Magento_Contacto: Estructura de los ficheros de Magento 2.4.5-p1
│ registration.php
│
├─Block
│ ContactForm.php
│
├─Controller
│ │ Index.php
│ │
│ └─Index
│ Index.php
│ Post.php
│
├─etc
│ │ acl.xml
│ │ config.xml
│ │ di.xml
│ │ email_templates.xml
│ │ module.xml
│ │
│ ├─adminhtml
│ │ system.xml
│ │
│ └─frontend
│ di.xml
│ page_types.xml
│ routes.xml
│
├─Helper
│ Data.php
│
├─i18n
│ en_US.csv
│
├─Model
│ │ Config.php
│ │ ConfigInterface.php
│ │ Mail.php
│ │ MailInterface.php
│ │
│ └─System
│ └─Config
│ └─Backend
│ Links.php
│
├─Plugin
│ └─UserDataProvider
│ ViewModel.php
│
├─view
│ └─frontend
│ ├─email
│ │ submitted_form.html
│ │
│ ├─layout
│ │ contact_index_index.xml
│ │ default.xml
│ │
│ └─templates
│ form.phtml
│
└─ViewModel
UserDataProvider.php
Vamos a explicar siguiendo el orden Controller - Vista - Modelo.
La url para acceder a la página del contacto es:
# Multisite con el flag del site activado
https://ejemplo.com/tienda/contact/index/index
o single site
https://ejemplo.com/contact/index/index
El contact es el frontName. Se define en la ruta app/code/Magento/Contact/etc/frontend/routes.xml
.
Los Controladores de este frontName están en app/code/Magento/Contact/Controller
. Las carpetas son el nombre de
los controladores y los ficheros son el ActionName. Estos ficheros tienen un método obligatoria execute().
Cuando accedemos a cualquier url relacionada con el frontname contact, se ejecutará el método del actionName.
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="contact" frontName="contact">
<module name="Magento_Contact" />
</route>
</router>
</config>
En este caso, el ActioName Index nos devuelve layout.
class Index extends \Magento\Contact\Controller\Index implements HttpGetActionInterface
{
/**
* Show Contact Us page
*
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
return $this->resultFactory->create(ResultFactory::TYPE_PAGE);
}
}
El layout asociado a esta url es app/code/Magento/Contact/view/frontend/layout/contact_index_index.xml
.
El contact es el id del route definido en routes.xml. El index es la carpeta que contienen el fichero php (actionName),
y el último index es el actionName.
El layout como su nombre indica es la estructura de la página. Vamos a ver qué estructura tiene.
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<title>Contact Us</title>
</head>
<body>
<referenceContainer name="content">
<block class="Magento\Contact\Block\ContactForm" name="contactForm" template="Magento_Contact::form.phtml">
<container name="form.additional.info" label="Form Additional Info"/>
</block>
</referenceContainer>
</body>
</page>
El layout es de 1 columna. Existen columnas de 1, 2 y 3. El nombre del título de la página es Contact Us.
En el contenedor content (espacio entre el header y footer), incluimos un fichero html, form.phtml. Este fichero
podrá usar propiedades y métodos del fichero php Magento\Contact\Block\ContactForm
.
En el fichero Magento_Contact::form.phtml
, vemos este código $viewModel = $block->getViewModel();
.
# En versiones anteriores, para incluir el viewmodel en el fichero phtml, se hizo así en contact_index_index
<block class="Magento\Contact\Block\ContactForm" name="contactForm" template="Magento_Contact::form.phtml">
<container name="form.additional.info" label="Form Additional Info"/>
<arguments>
<argument name="view_model" xsi:type="object">Magento\Contact\ViewModel\UserDataProvider</argument>
</arguments>
</block>
# En versiones actuales 2.4.5, descubrieron un problema. El formulario de contacto fallaba, si lo incluimos en un block o en un widget
# Por eso usaron esta manera para resolver el problema
# Crear objeto UserDataProvider
# Ruta: Contact/etc/frontend/di.xml
<type name="Magento\Contact\Plugin\UserDataProvider\ViewModel">
<arguments>
<argument name="viewModel" xsi:type="object">Magento\Contact\ViewModel\UserDataProvider</argument>
</arguments>
</type>
# app/code/Magento/Contact/Plugin/UserDataProvider/ViewModel.php
public function __construct(ArgumentInterface $viewModel)
{
$this->viewModel = $viewModel;
}
# Antes de pintar código de html, asignar el objeto anterior UserDataProvider a Block
<type name="Magento\Contact\Block\ContactForm">
<plugin name="set_view_model" type="Magento\Contact\Plugin\UserDataProvider\ViewModel" />
</type>
# app/code/Magento/Contact/Plugin/UserDataProvider/ViewModel.php
/**
* Sets the view model before rendering to HTML
*
* @param DataObject|BlockInterface $block
* @return null
*/
public function beforeToHtml(DataObject $block)
{
if (!$block->hasData(self::VIEW_MODEL)) {
$block->setData(self::VIEW_MODEL, $this->viewModel);
}
return null;
}
Envío del mensaje
Cuando el usuario acaba de rellenar los datos necesarios del formulario y da al botón "Enviar", se enviarán los datos en modo HTTP POST.
# Petición del tipo HTTP POST
https://ejemplo/tienda/contact/index/post/
# Datos enviados
name: Prueba
email: prueba@ejemplo.com
telephone: 888888888
comment: Esta es una prueba
g-recaptcha-response: Parámetro POST cuando el usuario envía el formulario en su sitio. Será validado por el api de Google.
hideit: Es un campo oculto para evitar Spam. Si el valor de este campo no está vacío, se aborta el proceso de envío.
token: Investigar para qué sirve
form_key: Sirve para prevenir ataques Cross Site Request Forgery. Esto es para evitar que hagan peticiones POST desde otros sitios webs o aplicaciones con malas intenciones.
Estos datos son validados y enviados al correo electrónico indicado en el Backoffice. Magento no los guarda en la Base de datos.
Envío de Mails
El envío del mail se realiza después de la validación del tipo de petición HTTP y datos.
# Ruta: app/code/Magento/Contact/Controller/Index/Post.php
public function execute()
{
if (!$this->getRequest()->isPost()) {
return $this->resultRedirectFactory->create()->setPath('*/*/');
}
try {
$this->sendEmail($this->validatedParams());
$this->messageManager->addSuccessMessage(
__('Thanks for contacting us with your comments and questions. We\'ll respond to you very soon.')
);
$this->dataPersistor->clear('contact_us');
return $this->resultRedirectFactory->create()->setPath('contact/index');
.....
# El método sendEmail usa el objeto mail, que es un inteface mapeado en app/code/Magento/Contact/etc/di.xml
<preference for="Magento\Contact\Model\MailInterface" type="Magento\Contact\Model\Mail" />
# Ruta: app/code/Magento/Contact/Controller/Index/Post.php
use Magento\Contact\Model\MailInterface;
/**
* @var MailInterface
*/
private $mail;
private function sendEmail($post)
{
$this->mail->send(
$post['email'],
['data' => new DataObject($post)]
);
}
Las configuraciones para el envío del Mail están en el fichero app/code/Magento/Contact/Model/ConfigInterface.php. Magento mapea el ConfigInterface a la clase Config del fichero app/code/Magento/Contact/Model/Config.php. El interface es como un alias de la clase real. Este alias es como un Jefe, que ordena hacer tareas a los empleados. La concreción de las tareas es cosa de los empleados. Así varios empleados pueden estar haciendo la misma tarea. Por ejemplo: El formulario de contacto envía mail. El formulario del pago también envía mail. Los dos formularios son diferentes, pero hacen la misma tarea, que es enviar mail. Claro, el contenido del mail es diferente.
# app/code/Magento/Contact/etc/di.xml
<preference for="Magento\Contact\Model\ConfigInterface" type="Magento\Contact\Model\Config" />
La plantilla usada para el envío del mail tiene una ruta de configuración: contact/email/email_template. Está definida en el Interface ConfigInterface. Esta ruta tiene valor, que puede ser tomado o desde el fichero config.xml o desde la columna path de la tabla core_config_data. La diferencia es que uno tiene el valor escrito en un fichero y otro, el valor es dinámico y está guardado en la base de datos. El valor en la base de datos tiene prioridad sobre el valor en el fichero. En este caso, el valor es tomado del fichero config.xml y es contact_email_email_template. Este valor es el identificador del template.
# app/code/Magento/Contact/etc/config.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
<default>
<contact>
<contact>
<enabled>1</enabled>
</contact>
<email>
<recipient_email>
<![CDATA[hello@example.com]]>
</recipient_email>
<sender_email_identity>custom2</sender_email_identity>
<email_template>contact_email_email_template</email_template>
</email>
</contact>
</default>
</config>
Los identificadores de los templates Email se definen en el fichero email_templates.xml, en la carpeta etc del módulo. Magento mezcla todos los ficheros email_templates.xml y según el identificador, asigna a un proceso del envío del correo electrónico.
# Ruta: app/code/Magento/Contact/etc/email_templates.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Email:etc/email_templates.xsd">
<template id="contact_email_email_template" label="Contact Form" file="submitted_form.html" type="html" module="Magento_Contact" area="frontend"/>
</config>
# El atributo file tiene el nombre del fichero de la plantilla, en este caso submitted_form.html
# Ruta: app/code/Magento/Contact/view/frontend/email/submitted_form.html
<!--@subject {{trans "Contact Form"}} @-->
<!--@vars {
"var data.comment":"Comment",
"var data.email":"Sender Email",
"var data.name":"Sender Name",
"var data.telephone":"Sender Telephone"
} @-->
{{template config_path="design/email/header_template"}}
<table class="message-details">
<tr>
<td><strong>{{trans "Name"}}</strong></td>
<td>{{var data.name}}</td>
</tr>
<tr>
<td><strong>{{trans "Email"}}</strong></td>
<td>{{var data.email}}</td>
</tr>
<tr>
<td><strong>{{trans "Phone"}}</strong></td>
<td>{{var data.telephone}}</td>
</tr>
</table>
<p><strong>{{trans "Message"}}</strong></p>
<p>{{var data.comment}}</p>
{{template config_path="design/email/footer_template"}}