In this tutorial, we will learn about how to add attachment (browse) button in Magento2 contact us form.
We will first override default contact us form with our module's phtml file. Add file upload buton in phtml file. And then override contact us post controller to make it work as per our custom logic.
Let's start it by creating custom module.
Find complete module on Github.
Create folder in
app/code/Magelearn/ContactAttachmentStep 1: Create module registration file
Add registration.php file in it:
<?php /** * Copyright © All rights reserved. * See COPYING.txt for license details. */ use Magento\Framework\Component\ComponentRegistrar; ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_ContactAttachment', __DIR__);Add composer.json file in it:
{
"name": "magelearn/contactattachment",
"description": "Magento 2 add attachment to attach files to the contact us form",
"type": "magento2-module",
"license": "OSL-3.0",
"authors": [
{
"email": "info@mage2gen.com",
"name": "Mage2Gen"
},
{
"email": "vijaymrami@gmail.com",
"name": "vijay rami"
}
],
"minimum-stability": "dev",
"require": {},
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Magelearn\\ContactAttachment\\": ""
}
}
}Add etc/module.xml file in it:
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Magelearn_ContactAttachment" setup_version="1.0.0">
<sequence>
<module name="Magento_Contact"/>
<module name="Magento_Framework"/>
</sequence>
</module>
</config>Now first let's modify default Magento2 Contact us form HTML with layout xml file.
Add contact_index_index.xml file in
view/frontend/layout<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="contactForm" remove="true"/>
<referenceContainer name="content">
<block class="Magento\Contact\Block\ContactForm" name="magelearn.contact.attachment" template="Magelearn_ContactAttachment::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>
</referenceContainer>
</body>
</page>Now add our custom phtml file.
Add form.phtml file in view/frontend/templates
<?php
/**
* Copyright 2011 Adobe
* All Rights Reserved.
*/
// phpcs:disable Magento2.Templates.ThisInTemplate
// phpcs:disable Generic.Files.LineLength.TooLong
/** @var \Magento\Contact\Block\ContactForm $block */
/** @var \Magento\Framework\View\Element\ButtonLockManager|null $buttonLockManager */
$buttonLockManager = $block->getData('button_lock_manager');
/** @var $escaper \Magento\Framework\Escaper */
/** @var \Magento\Contact\ViewModel\UserDataProvider $viewModel */
$viewModel = $block->getViewModel();
?>
<form class="form contact"
action="<?= $escaper->escapeUrl($block->getFormAction()) ?>"
id="contact-form"
method="post"
data-hasrequired="<?= $escaper->escapeHtmlAttr(__('* Required Fields')) ?>"
data-mage-init='{"validation":{}}'
enctype="multipart/form-data">
<fieldset class="fieldset">
<legend class="legend"><span><?= $escaper->escapeHtml(__('Write Us')) ?></span></legend><br />
<div class="field note no-label">
<?= $escaper->escapeHtml(__('Jot us a note and we’ll get back to you as quickly as possible.')) ?>
</div>
<div class="field name required">
<label class="label" for="name"><span><?= $escaper->escapeHtml(__('Name')) ?></span></label>
<div class="control">
<input name="name"
id="name"
title="<?= $escaper->escapeHtmlAttr(__('Name')) ?>"
value="<?= $escaper->escapeHtmlAttr($viewModel->getUserName()) ?>"
class="input-text"
type="text"
data-validate="{required:true}"/>
</div>
</div>
<div class="field email required">
<label class="label" for="email"><span><?= $escaper->escapeHtml(__('Email')) ?></span></label>
<div class="control">
<input name="email"
id="email"
title="<?= $escaper->escapeHtmlAttr(__('Email')) ?>"
value="<?= $escaper->escapeHtmlAttr($viewModel->getUserEmail()) ?>"
class="input-text"
type="email"
data-validate="{required:true, 'validate-email':true}"
data-mage-init='{"mage/trim-input":{}}'
/>
</div>
</div>
<div class="field telephone">
<label class="label" for="telephone"><span><?= $escaper->escapeHtml(__('Phone Number')) ?></span></label>
<div class="control">
<input name="telephone"
id="telephone"
title="<?= $escaper->escapeHtmlAttr(__('Phone Number')) ?>"
value="<?= $escaper->escapeHtmlAttr($viewModel->getUserTelephone()) ?>"
class="input-text"
type="tel" />
</div>
</div>
<div class="field comment required">
<label class="label" for="comment">
<span><?= $escaper->escapeHtml(__('What’s on your mind?')) ?></span>
</label>
<div class="control">
<textarea name="comment"
id="comment"
title="<?= $escaper->escapeHtmlAttr(__('What’s on your mind?')) ?>"
class="input-text"
cols="5"
rows="3"
data-validate="{required:true}"
><?= $escaper->escapeHtml($viewModel->getUserComment()) ?></textarea>
</div>
</div>
<div class="field attachment">
<label class="label" for="attachment"><span><?= $block->escapeHtml(__('Attachment')) ?></span></label>
<div class="control">
<input name="attachment" id="attachment" title="<?= $block->escapeHtmlAttr(__('Attachment')) ?>" class="input-text" type="file"/>
</div>
</div>
<?= $block->getChildHtml('form.additional.info') ?>
</fieldset>
<div class="actions-toolbar">
<div class="primary">
<input type="hidden" name="hideit" id="hideit" value="" />
<button type="submit" title="<?= $escaper->escapeHtmlAttr(__('Submit')) ?>" class="action submit primary"
id="send2"
<?php if ($buttonLockManager?->isDisabled('contact_us_form_submit')): ?>
disabled="disabled"
<?php endif; ?>>
<span><?= $escaper->escapeHtml(__('Submit')) ?></span>
</button>
</div>
</div>
</form>
<script type="text/x-magento-init">
{
"*": {
"Magento_Customer/js/block-submit-on-send": {
"formId": "contact-form"
}
}
}
</script>Now we will add a Plugn `afterGetTransport` which allows us to extend Magento 2.4.9's native email flow without overriding core classes such as `TransportBuilder`, `Mail`, or the Contact controller.
Once `getTransport()` creates the email message, the plugin accesses Magento's underlying Symfony MIME message and adds the uploaded file as a `DataPart`.
The existing email body is preserved and wrapped with `MixedPart`, resulting in a standard `multipart/mixed` email containing both the original message and the attachment.
Now, Considering all above point Add etc/frontend/di.xml file:<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\Mail\Template\TransportBuilder">
<plugin name="magelearn_contact_attachment"
type="Magelearn\ContactAttachment\Plugin\TransportBuilderPlugin"
sortOrder="100"/>
</type>
</config>Add Plugin/TransportBuilderPlugin.php file<?php
declare(strict_types=1);
namespace Magelearn\ContactAttachment\Plugin;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Mail\TransportInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\MixedPart;
class TransportBuilderPlugin
{
private const CONTACT_ACTION = 'contact_index_post';
private const FILE_FIELD = 'attachment';
private const MAX_FILE_SIZE = 10485760; // 10 MB
/**
* Allowed file extensions.
*/
private const ALLOWED_EXTENSIONS = [
'txt',
'csv',
'jpg',
'jpeg',
'gif',
'png',
'pdf',
'doc',
'docx',
];
/**
* Allowed MIME types.
*/
private const ALLOWED_MIME_TYPES = [
'text/plain',
'text/csv',
'image/jpeg',
'image/gif',
'image/png',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
];
/**
* @param RequestInterface $request
* @param LoggerInterface $logger
*/
public function __construct(
private readonly RequestInterface $request,
private readonly LoggerInterface $logger
) {
}
/**
* Add the Contact Us attachment to the generated email.
*
* @param TransportBuilder $subject
* @param TransportInterface $transport
* @return TransportInterface
* @throws LocalizedException
*/
public function afterGetTransport(
TransportBuilder $subject,
TransportInterface $transport
): TransportInterface {
if ($this->request->getFullActionName() !== self::CONTACT_ACTION) {
return $transport;
}
$file = $this->request->getFiles(self::FILE_FIELD);
if (!is_array($file) || !$this->hasUploadedFile($file)) {
return $transport;
}
$this->validateFile($file);
$filePath = (string) $file['tmp_name'];
$fileName = $this->sanitizeFileName((string) $file['name']);
$mimeType = $this->detectMimeType($filePath);
$message = $transport->getMessage();
$symfonyMessage = $message->getSymfonyMessage();
$body = $symfonyMessage->getBody();
if ($body === null) {
throw new LocalizedException(
__('Unable to create the email attachment.')
);
}
$attachment = DataPart::fromPath(
$filePath,
$fileName,
$mimeType
);
$symfonyMessage->setBody(
new MixedPart(
$body,
$attachment
)
);
return $transport;
}
/**
* Check whether a valid uploaded file exists.
*
* @param array<string, mixed> $file
* @return bool
*/
private function hasUploadedFile(array $file): bool
{
return isset(
$file['error'],
$file['tmp_name'],
$file['name'],
$file['size']
) && (int) $file['error'] !== UPLOAD_ERR_NO_FILE;
}
/**
* Validate uploaded attachment.
*
* @param array<string, mixed> $file
* @return void
* @throws LocalizedException
*/
private function validateFile(array $file): void
{
$uploadError = (int) $file['error'];
if ($uploadError !== UPLOAD_ERR_OK) {
throw new LocalizedException(
__('Unable to upload the attachment. Please try again.')
);
}
$filePath = (string) $file['tmp_name'];
if (!is_uploaded_file($filePath)) {
throw new LocalizedException(
__('The uploaded attachment is invalid.')
);
}
if (!is_readable($filePath)) {
throw new LocalizedException(
__('The uploaded attachment cannot be read.')
);
}
$fileSize = (int) $file['size'];
if ($fileSize <= 0) {
throw new LocalizedException(
__('The uploaded attachment is empty.')
);
}
if ($fileSize > self::MAX_FILE_SIZE) {
throw new LocalizedException(
__('The attachment exceeds the maximum allowed size of 10 MB.')
);
}
$extension = strtolower(
(string) pathinfo(
(string) $file['name'],
PATHINFO_EXTENSION
)
);
if (!in_array($extension, self::ALLOWED_EXTENSIONS, true)) {
throw new LocalizedException(
__('The selected attachment file type is not allowed.')
);
}
$mimeType = $this->detectMimeType($filePath);
if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)) {
throw new LocalizedException(
__('The selected attachment file type is not allowed.')
);
}
}
/**
* Detect the actual MIME type of the uploaded file.
*
* @param string $filePath
* @return string
* @throws LocalizedException
*/
private function detectMimeType(string $filePath): string
{
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($filePath);
if (!is_string($mimeType) || $mimeType === '') {
throw new LocalizedException(
__('Unable to determine the attachment file type.')
);
}
return $mimeType;
}
/**
* Sanitize the attachment filename.
*
* @param string $fileName
* @return string
*/
private function sanitizeFileName(string $fileName): string
{
$fileName = basename($fileName);
$sanitizedFileName = preg_replace(
'/[^A-Za-z0-9._-]/',
'_',
$fileName
);
return is_string($sanitizedFileName) && $sanitizedFileName !== ''
? $sanitizedFileName
: 'attachment';
}
}Add email_templates.xml file inside etc/<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<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_attachment_email_template" label="Contact Form with Attachment" file="submitted_form.html" type="html" module="Magelearn_ContactAttachment" area="frontend"/>
</config>Add submitted_form.html file inside view/frontend/email/<!--@subject {{trans "Contact Form with Attachment"}} @-->
<!--@vars {
"var data.comment":"Comment",
"var data.email":"Sender Email",
"var data.name":"Sender Name",
"var data.telephone":"Sender Telephone",
"var data.attachment":"Sender Attachment"
} @-->
{{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>
<tr>
<td><strong>{{trans "Attachment"}}</strong></td>
<td>{{var data.attachment}}</td>
</tr>
</table>
<p><strong>{{trans "Message"}}</strong></p>
<p>{{var data.comment}}</p>
{{template config_path="design/email/footer_template"}}After adding above files just run below commands:
php bin/magento set:upgphp bin/magento set:d:cphp bin/magento set:s:d -f en_USphp bin/magento c:cphp bin/magento set:c:f
You will find new browse buton will be added in contactus form and after submitting it admin email receipt will get email notification with attachment in mail.
Tag :
Magento2,
Magento2 Extensions
0 Comments On "Magento 2 add attachment to attach files to the contact us form"