Magento 2.4.2 How to attched PDF in Email?
-
12-05-2021 - |
Question
I want to attach PDF in custom Email template but it not working having some error like
PHP Fatal error: Uncaught Error: Call to a member function createAttachment() on null in /var/www/html/app/code/Magento360/Base/Model/Mail/TransportBuilder.php:8
Stack trace:
ù0 /var/www/html/generated/code/Magento360/Base/Model/Mail/TransportBuilder/Interceptor.php(23):
I have tried following solution but non of are working for me
- https://webkul.com/blog/attach-pdf-file-magento-2-email/
- https://meetanshi.com/blog/programmatically-attach-pdf-file-in-magento-2-email/
- https://github.com/mageplaza/magento-2-email-attachments/issues/4
Does anyone have an idea what's wrong with my code? is there anything changed in 2.4.2 for File Attachment.
Solution
Here custom career module code with a file attachment in an email. You can download the whole code from here
- Controller sends email action with attachment. Mital/Careers/Controller/Index/Save.php
<?php
namespace Mital\Careers\Controller\Index;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Action\Action;
use Magento\Framework\Controller\ResultFactory;
class Save extends Action
{
protected $_resultPageFactory;
protected $_modelDataFactory;
public function __construct(
Context $context,
\Magento\Framework\View\Result\PageFactory $resultPageFactory,
\Magento\MediaStorage\Model\File\UploaderFactory $uploader,
\Magento\Framework\Filesystem $filesystem,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Mital\Careers\Model\Mail\Template\TransportBuilder $transportBuilder,
\Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
)
{
$this->_resultPageFactory = $resultPageFactory;
$this->uploader = $uploader;
$this->filesystem = $filesystem;
$this->_scopeConfig = $scopeConfig;
$this->_transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
parent::__construct($context);
}
public function execute()
{
try{
$request = $this->getRequest()->getParams();
if(isset($_FILES['myfile']) && isset($_FILES['myfile']['name']) && strlen($_FILES['myfile']['name'])){
$base_media_path = 'Mital/Careers';
$uploader = $this->uploader->create(
['fileId' => 'myfile']
);
$uploader->setAllowedExtensions(['pdf','doc','docx','odt']);
$uploader->setAllowRenameFiles(true);
$uploader->setFilesDispersion(true);
$mediaDirectory = $this->filesystem->getDirectoryRead(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
$result = $uploader->save(
$mediaDirectory->getAbsolutePath($base_media_path)
);
$data['myfile'] = $base_media_path.$result['file'];
$filePath = $result['path'].$result['file'];
$fileName = $result['name'];
}
$templateVars = [
'name' => $request['your_name'],
'message' => $request['your_message'],
'email' => $request['email_address'],
'telephone' => $request['your_telephone']
];
$fromEmail= $this->getRequest()->getPostValue('email_address');
$fromName = $this->getRequest()->getPostValue('your_name');
$from = ['email' => $fromEmail, 'name' => $fromName];
$this->inlineTranslation->suspend();
$to = $this->_scopeConfig->getValue(
'contact/email/recipient_email',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
$templateOptions = [
'area' => \Magento\Framework\App\Area::AREA_FRONTEND,
'store' => 1
];
if(isset($fileName)){
$transport = $this->_transportBuilder->setTemplateIdentifier('careers_email_template')
->setTemplateOptions($templateOptions)
->setTemplateVars($templateVars)
->addAttachment(file_get_contents($filePath),$fileName ,'application/pdf')
->setFrom($from)
->addTo($to)
->getTransport();
}else{
$transport = $this->_transportBuilder->setTemplateIdentifier('careers_email_template')
->setTemplateOptions($templateOptions)
->setTemplateVars($templateVars)
->setFrom($from)
->addTo($to)
->getTransport();
}
$transport->sendMessage();
$this->inlineTranslation->resume();
$this->messageManager->addSuccess(__('Form successfully submitted'));
$redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
$redirect->setUrl($this->_redirect->getRefererUrl());
return $redirect;
}catch (\Exception $e) {
$this->messageManager->addError(__($e->getMessage()));
}
}
}
?>
- Create a custom transport builder for send an attachment Mital/Careers/Model/Mail/Template/TransportBuilder.php
<?php
declare(strict_types=1);
namespace Mital\Careers\Model\Mail\Template;
use Magento\Framework\App\TemplateTypesInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Mail\EmailMessageInterface;
use Magento\Framework\Mail\EmailMessageInterfaceFactory;
use Magento\Framework\Mail\AddressConverter;
use Magento\Framework\Mail\Exception\InvalidArgumentException;
use Magento\Framework\Mail\MessageInterface;
use Magento\Framework\Mail\MessageInterfaceFactory;
use Magento\Framework\Mail\MimeInterface;
use Magento\Framework\Mail\MimeMessageInterfaceFactory;
use Magento\Framework\Mail\MimePartInterfaceFactory;
use Magento\Framework\Mail\Template\FactoryInterface;
use Magento\Framework\Mail\Template\SenderResolverInterface;
use Magento\Framework\Mail\TemplateInterface;
use Magento\Framework\Mail\TransportInterface;
use Magento\Framework\Mail\TransportInterfaceFactory;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Phrase;
use Zend\Mime\Mime;
use Zend\Mime\PartFactory;
/**
* TransportBuilder
*
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
*/
class TransportBuilder extends \Magento\Framework\Mail\Template\TransportBuilder
{
/**
* Template Identifier
*
* @var string
*/
protected $templateIdentifier;
/**
* Template Model
*
* @var string
*/
protected $templateModel;
/**
* Template Variables
*
* @var array
*/
protected $templateVars;
/**
* Template Options
*
* @var array
*/
protected $templateOptions;
/**
* Mail Transport
*
* @var TransportInterface
*/
protected $transport;
/**
* Template Factory
*
* @var FactoryInterface
*/
protected $templateFactory;
/**
* Object Manager
*
* @var ObjectManagerInterface
*/
protected $objectManager;
/**
* Message
*
* @var EmailMessageInterface
*/
protected $message;
/**
* Sender resolver
*
* @var SenderResolverInterface
*/
protected $_senderResolver;
/**
* @var TransportInterfaceFactory
*/
protected $mailTransportFactory;
/**
* Param that used for storing all message data until it will be used
*
* @var array
*/
private $messageData = [];
/**
* @var EmailMessageInterfaceFactory
*/
private $emailMessageInterfaceFactory;
/**
* @var MimeMessageInterfaceFactory
*/
private $mimeMessageInterfaceFactory;
/**
* @var MimePartInterfaceFactory
*/
private $mimePartInterfaceFactory;
/**
* @var AddressConverter|null
*/
private $addressConverter;
protected $attachments = [];
protected $partFactory;
/**
* TransportBuilder constructor
*
* @param FactoryInterface $templateFactory
* @param MessageInterface $message
* @param SenderResolverInterface $senderResolver
* @param ObjectManagerInterface $objectManager
* @param TransportInterfaceFactory $mailTransportFactory
* @param MessageInterfaceFactory|null $messageFactory
* @param EmailMessageInterfaceFactory|null $emailMessageInterfaceFactory
* @param MimeMessageInterfaceFactory|null $mimeMessageInterfaceFactory
* @param MimePartInterfaceFactory|null $mimePartInterfaceFactory
* @param addressConverter|null $addressConverter
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
FactoryInterface $templateFactory,
MessageInterface $message,
SenderResolverInterface $senderResolver,
ObjectManagerInterface $objectManager,
TransportInterfaceFactory $mailTransportFactory,
MessageInterfaceFactory $messageFactory = null,
EmailMessageInterfaceFactory $emailMessageInterfaceFactory = null,
MimeMessageInterfaceFactory $mimeMessageInterfaceFactory = null,
MimePartInterfaceFactory $mimePartInterfaceFactory = null,
AddressConverter $addressConverter = null
) {
$this->templateFactory = $templateFactory;
$this->objectManager = $objectManager;
$this->_senderResolver = $senderResolver;
$this->mailTransportFactory = $mailTransportFactory;
$this->emailMessageInterfaceFactory = $emailMessageInterfaceFactory ?: $this->objectManager
->get(EmailMessageInterfaceFactory::class);
$this->mimeMessageInterfaceFactory = $mimeMessageInterfaceFactory ?: $this->objectManager
->get(MimeMessageInterfaceFactory::class);
$this->mimePartInterfaceFactory = $mimePartInterfaceFactory ?: $this->objectManager
->get(MimePartInterfaceFactory::class);
$this->addressConverter = $addressConverter ?: $this->objectManager
->get(AddressConverter::class);
$this->partFactory = $objectManager->get(PartFactory::class);
parent::__construct($templateFactory, $message, $senderResolver, $objectManager, $mailTransportFactory, $messageFactory, $emailMessageInterfaceFactory, $mimeMessageInterfaceFactory,
$mimePartInterfaceFactory, $addressConverter);
}
/**
* Add cc address
*
* @param array|string $address
* @param string $name
*
* @return $this
*/
public function addCc($address, $name = '')
{
$this->addAddressByType('cc', $address, $name);
return $this;
}
/**
* Add to address
*
* @param array|string $address
* @param string $name
*
* @return $this
* @throws InvalidArgumentException
*/
public function addTo($address, $name = '')
{
$this->addAddressByType('to', $address, $name);
return $this;
}
/**
* Add bcc address
*
* @param array|string $address
*
* @return $this
* @throws InvalidArgumentException
*/
public function addBcc($address)
{
$this->addAddressByType('bcc', $address);
return $this;
}
/**
* Set Reply-To Header
*
* @param string $email
* @param string|null $name
*
* @return $this
* @throws InvalidArgumentException
*/
public function setReplyTo($email, $name = null)
{
$this->addAddressByType('replyTo', $email, $name);
return $this;
}
/**
* Set mail from address
*
* @param string|array $from
*
* @return $this
* @throws InvalidArgumentException
* @see setFromByScope()
*
* @deprecated 102.0.1 This function sets the from address but does not provide
* a way of setting the correct from addresses based on the scope.
*/
public function setFrom($from)
{
return $this->setFromByScope($from);
}
/**
* Set mail from address by scopeId
*
* @param string|array $from
* @param string|int $scopeId
*
* @return $this
* @throws InvalidArgumentException
* @throws MailException
* @since 102.0.1
*/
public function setFromByScope($from, $scopeId = null)
{
$result = $this->_senderResolver->resolve($from, $scopeId);
$this->addAddressByType('from', $result['email'], $result['name']);
return $this;
}
/**
* Set template identifier
*
* @param string $templateIdentifier
*
* @return $this
*/
public function setTemplateIdentifier($templateIdentifier)
{
$this->templateIdentifier = $templateIdentifier;
return $this;
}
/**
* Set template model
*
* @param string $templateModel
*
* @return $this
*/
public function setTemplateModel($templateModel)
{
$this->templateModel = $templateModel;
return $this;
}
/**
* Set template vars
*
* @param array $templateVars
*
* @return $this
*/
public function setTemplateVars($templateVars)
{
$this->templateVars = $templateVars;
return $this;
}
/**
* Set template options
*
* @param array $templateOptions
* @return $this
*/
public function setTemplateOptions($templateOptions)
{
$this->templateOptions = $templateOptions;
return $this;
}
/**
* Get mail transport
*
* @return TransportInterface
* @throws LocalizedException
*/
public function getTransport()
{
try {
$this->prepareMessage();
$mailTransport = $this->mailTransportFactory->create(['message' => clone $this->message]);
} finally {
$this->reset();
}
return $mailTransport;
}
/**
* Reset object state
*
* @return $this
*/
protected function reset()
{
$this->messageData = [];
$this->templateIdentifier = null;
$this->templateVars = null;
$this->templateOptions = null;
return $this;
}
/**
* Get template
*
* @return TemplateInterface
*/
protected function getTemplate()
{
return $this->templateFactory->get($this->templateIdentifier, $this->templateModel)
->setVars($this->templateVars)
->setOptions($this->templateOptions);
}
/**
* Prepare message.
*
* @return $this
* @throws LocalizedException if template type is unknown
*/
protected function prepareMessage()
{
$template = $this->getTemplate();
$content = $template->processTemplate();
switch ($template->getType()) {
case TemplateTypesInterface::TYPE_TEXT:
$part['type'] = MimeInterface::TYPE_TEXT;
break;
case TemplateTypesInterface::TYPE_HTML:
$part['type'] = MimeInterface::TYPE_HTML;
break;
default:
throw new LocalizedException(
new Phrase('Unknown template type')
);
}
$mimePart = $this->mimePartInterfaceFactory->create(['content' => $content]);
$parts = count($this->attachments) ? array_merge([$mimePart], $this->attachments) : [$mimePart];
$this->messageData['body'] = $this->mimeMessageInterfaceFactory->create(
['parts' => $parts]
);
$this->messageData['subject'] = html_entity_decode(
(string)$template->getSubject(),
ENT_QUOTES
);
$this->message = $this->emailMessageInterfaceFactory->create($this->messageData);
return $this;
}
/**
* Handles possible incoming types of email (string or array)
*
* @param string $addressType
* @param string|array $email
* @param string|null $name
*
* @return void
* @throws InvalidArgumentException
*/
private function addAddressByType(string $addressType, $email, ?string $name = null): void
{
if (is_string($email)) {
$this->messageData[$addressType][] = $this->addressConverter->convert($email, $name);
return;
}
$convertedAddressArray = $this->addressConverter->convertMany($email);
if (isset($this->messageData[$addressType])) {
$this->messageData[$addressType] = array_merge(
$this->messageData[$addressType],
$convertedAddressArray
);
}
}
/**
* @param string|null $content
* @param string|null $fileName
* @param string|null $fileType
* @return TransportBuilder
*/
public function addAttachment(?string $content, ?string $fileName, ?string $fileType)
{
$attachmentPart = $this->partFactory->create();
$attachmentPart->setContent($content)
->setType($fileType)
->setFileName($fileName)
->setDisposition(Mime::DISPOSITION_ATTACHMENT)
->setEncoding(Mime::ENCODING_BASE64);
$this->attachments[] = $attachmentPart;
return $this;
}
}
You can download module for custom form with email attachment from here
OTHER TIPS
This should work:)
on your di.xml Vendor\Module\etc\di.xml
<preference for="Magento\Sales\Model\Order\Email\SenderBuilder" type="Module\Vendor\Model\Mail\SenderBuilder"/>
On Vendor\Module\Model\Order
<?php
namespace Vendor\Module\Model\Mail;
class SenderBuilder extends \Magento\Sales\Model\Order\Email\SenderBuilder
{
/**
* Prepare and send email message
*
* @return void
*/
public function send()
{
$vars = $this->templateContainer->getTemplateVars();
$order = $vars['order'];
if(isset( $order))
{
$fileName = get your file name here
$file = get your file here;
$this->transportBuilder->addAttachment($file, $fileName, 'application/pdf');
}
$this->configureEmailTemplate();
$this->transportBuilder->addTo(
$this->identityContainer->getCustomerEmail(),
$this->identityContainer->getCustomerName()
);
$copyTo = $this->identityContainer->getEmailCopyTo();
if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == 'bcc') {
foreach ($copyTo as $email) {
$this->transportBuilder->addBcc($email);
}
}
$transport = $this->transportBuilder->getTransport();
$transport->sendMessage();
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange