After updating Laravel to version 9, I can't sign an email inside a Mailable class - email

After updating Laravel to version 9, I can't sign an email inside a Mailable class.
my code in Laravel 6 worked:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NotificationEmail extends Mailable
{
use Queueable, SerializesModels;
protected $person;
protected $data = [];
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($person, $data)
{
$this->person = $person;
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('mail.notification.senat')
->replyTo('noreply#example.com')
->subject('Test subject')
->with([
'person' => $this->person,
'data' => $this->data
])
->withSwiftMessage(function ($message){
$smimeSigner = new \Swift_Signers_SMimeSigner(
config('mail.sign_cert'),
[config('mail.sign_key'), config('mail.sign_key_password')]
);
$message->attachSigner($smimeSigner);
});
}
}
my code in Laravel 9 does not work:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Symfony\Component\Mime\Crypto\SMimeSigner;
use Symfony\Component\Mime\Email;
use Illuminate\Support\Facades\Log;
class NotificationEmail extends Mailable
{
use Queueable, SerializesModels;
protected $person;
protected $data = [];
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($person, $data)
{
$this->person = $person;
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build(): NotificationEmail {
return $this->view('mail.notification.senat')
->replyTo('noreply#example.com')
->subject('Test subject')
->with([
'person' => $this->person,
'data' => $this->data
])
->withSymfonyMessage(function (Email $message){
$certPath = storage_path(env('MAIL_SIGN_CERT'));
$keyPath = storage_path(env('MAIL_SIGN_KEY'));
// see: https://symfony.com/doc/current/mailer.html#signing-and-encrypting-messages
$signer = new SMimeSigner($certPath, $keyPath, env('MAIL_SIGN_KEY_PASSWORD'));
$signer->sign($message);
})
;
}
}
I know that Laravel 9 works with Symfony Mailer. However, the description does not use a Laravel Mailable environment.... See: https://symfony.com/doc/current/mailer.html#signing-and-encrypting-messages
The Laravel9 doc shows the way to customize: https://laravel.com/docs/9.x/mail#customizing-the-symfony-message
But my solution does not work. I get an error, line 48 "$signer->sign($message);"
A message must have a text or an HTML part or attachments.
Do you have a clue and can you help?

I would suggest an event listener for any outbound mail to achieve signing for all outbound mails with Laravel 9 / Symfony Mailer.
<?php
namespace App\Listeners\Mail;
use Illuminate\Mail\Events\MessageSending;
use Symfony\Component\Mime\Crypto\SMimeSigner;
use Symfony\Component\Mime\Email;
class MessageSendingListener
{
public function handle(MessageSending $event)
{
$signer = new SMimeSigner('/path/to/certificate.pem', 'path/to/private.key');
$signedMessage = $signer->sign($event->message);
$event->message->setHeaders($signedMessage->getHeaders());
$event->message->setBody($signedMessage->getBody());
}
}
Full explanation with the Event Service Provider:
https://laracasts.com/discuss/channels/laravel/laravel-9-outbound-smime-messages
Symfony Docs S/MIME:
https://symfony.com/doc/current/mailer.html#s-mime-signer
Cheers :)

Related

Magneto 2 Admin grid custom column filter is not working in my module

create a module for custom column but still filter is not working
namespace Vendore\Modulename\Model\ResourceModel\modulename\Grid;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
use Psr\Log\LoggerInterface as Logger;
class Collection extends SearchResult
public function __construct(
EntityFactory $entityFactory,
Logger $logger,
FetchStrategy $fetchStrategy,
EventManager $eventManager,
$mainTable = 'unicommerce_sync_order',
$resourceModel = 'vendorename\modulename\Model\ResourceModel\modulename',
$identifierName = null,
$connectionName = null
) {
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel, $identifierName, $connectionName);
}
/**
* #return Collection|void
*/
protected function _initSelect()
{
$this->addFilterToMap('created_at', 'salesOrder.created_at');
parent::_initSelect();
}
protected function _renderFiltersBefore()
{
$this->getSelect()
->joinLeft(
['salesOrder' => $this->getConnection()->getTableName('sales_order')],
'main_table.magento_order_id = salesOrder.entity_id',
['created_at'=>'salesOrder.created_at']
);
//echo $this->getSelect();
parent::_renderFiltersBefore();
}
Note : create_at column date get proper but filter is not working
please check my code and let me know if any issue
Thanks

Unit Test for FormErrorSerializer in Symfony 4 - always valid form

I am trying to write a unit test for FormErrorSerializer that converts Symfony $form->getErrors() to a readable array.
My current approach is to create the form, give it data, and look for validation errors, but form is always valid. I don't get any errors no matter what data I provide to form.
In normal REST request/response it is working well and I am getting appropriate error message. I need help with getting the error messages in unit test.
namespace App\Tests\Unit;
use App\Form\UserType;
use App\Serializer\FormErrorSerializer;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Translation\Translator;
class FormErrorSerializerTest extends TypeTestCase
{
/**
* ValidatorExtensionTrait needed for invalid_options
* https://github.com/symfony/symfony/issues/22593
*/
use ValidatorExtensionTrait;
public function testConvertFormToArray(){
$form_data = [
'email' => 'test',
'plainPassword' => [
'pass' => '1',
'pass2' => '2'
]
];
$translator = new Translator('de');
$form = $this->factory->create(UserType::class);
$form->submit($form_data);
if( $form->isValid() ) {
echo "Form is valid"; exit;
}
$formErrorSerializer = new FormErrorSerializer($translator);
$errors = $formErrorSerializer->convertFormToArray($form);
print_r($errors); exit;
}
}
Find below the Serializer:
namespace App\Serializer;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Serializes invalid Form instances.
*/
class FormErrorSerializer
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function convertFormToArray(FormInterface $data)
{
$form = $errors = [];
foreach ($data->getErrors() as $error) {
$errors[] = $this->getErrorMessage($error);
}
if ($errors) {
$form['errors'] = $errors;
}
$children = [];
foreach ($data->all() as $child) {
if ($child instanceof FormInterface) {
$children[$child->getName()] = $this->convertFormToArray($child);
}
}
if ($children) {
$form['children'] = $children;
}
return $form;
}
private function getErrorMessage(FormError $error)
{
if (null !== $error->getMessagePluralization()) {
return $this->translator->transChoice(
$error->getMessageTemplate(),
$error->getMessagePluralization(),
$error->getMessageParameters(),
'validators'
);
}
return $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), 'validators');
}
}
Ok, I was able to do this in 2 different ways.
First solution was to load the validator in getExtensions method. The factory in TypeTestCase doesn't bring the validator with it. So, not only you have to load the validator but you also have to explicitly specify the validations. You can specify validation using methods provided by symfony or you can directly point validator to the YAML or xml file if you are using one.
public function getExtensions()
{
$validator = (new ValidatorBuilder())
->addYamlMapping("path_to_validations.yaml")
->setConstraintValidatorFactory(new ConstraintValidatorFactory())
->getValidator();
$extensions[] = new CoreExtension();
$extensions[] = new ValidatorExtension($validator);
return $extensions;
}
However, I didn't use the above approach. I went with even better solution. Due to high complexity of my test case (as it needed multiple services), I went with a special container provided by Symfony's KernelTestCase. It provides private services in tests, and the factory it provides comes with validator and validations, just like you code in controller. You do not need to load validator explicitly. Find below my final test that extends KernelTestCase.
namespace App\Tests\Unit\Serializer;
use App\Entity\User;
use App\Form\UserType;
use App\Serializer\FormErrorSerializer;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Translation\TranslatorInterface;
class FormErrorSerializerTest extends KernelTestCase
{
/**
* {#inheritDoc}
*/
protected function setUp()
{
$kernel = self::bootKernel();
}
public function testConvertFormToArray_invalidData(){
$form_data = [
'email' => 'test',
'plainPassword' => [
'pass' => '1111',
'pass2' => ''
]
];
$user = new User();
$user->setEmail($form_data['email']);
$user->setPlainPassword($form_data['plainPassword']['pass']);
$factory = self::$container->get(FormFactoryInterface::class);
/**
* #var FormInterface $form
*/
$form = $factory->create(UserType::class, $user);
$form->submit($form_data);
$this->assertTrue($form->isSubmitted());
$this->assertFalse($form->isValid());
$translator = self::$container->get(TranslatorInterface::class);
$formErrorSerializer = new FormErrorSerializer($translator);
$errors = $formErrorSerializer->convertFormToArray($form);
$this->assertArrayHasKey('errors', $errors['children']['email']);
$this->assertArrayHasKey('errors', $errors['children']['plainPassword']['children']['pass']);
}
public function testConvertFormToArray_validData(){
$form_data = [
'email' => 'test#example.com',
'plainPassword' => [
'pass' => 'somepassword#slkd12',
'pass2' => 'somepassword#slkd12'
]
];
$user = new User();
$user->setEmail($form_data['email']);
$user->setPlainPassword($form_data['plainPassword']['pass']);
$factory = self::$container->get(FormFactoryInterface::class);
/**
* #var FormInterface $form
*/
$form = $factory->create(UserType::class, $user);
$form->submit($form_data);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isValid());
$translator = self::$container->get(TranslatorInterface::class);
$formErrorSerializer = new FormErrorSerializer($translator);
$errors = $formErrorSerializer->convertFormToArray($form);
$this->assertArrayNotHasKey('errors', $errors['children']['email']);
$this->assertArrayNotHasKey('errors', $errors['children']['plainPassword']['children']['pass']);
}
}
Please note that Symfony 4.1 has a special container that allows fetching private services.
self::$kernel->getContainer(); is not special container. It will not fetch private services.
However, self::$container; is special container that provides private services in testing.
More about this here.

Magento2 Custom Email send?

I added custom html form in magento2 page. In magento2 default contact form will be there, it will work normally.But my custom email not send. How to send the custom mail with user and admin?
1. Create new html file
/app/code/VendorName/ModuleName/view/frontend/email/modulename/test.html
<div>
Hello, Test email.
</div>
2. Declare your email template, create xml file
/app/code/VendorName/ModuleName/etc/email_templates.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:Magento:module:Magento_Email:etc/email_templates.xsd">
<template id="modulename_test_template" label="Test email" file="modulename/test.html" type="html" module="VendorName_ModuleName" area="frontend"/>
</config>
3. Finally, create a function to send your email.
namespace VendorName\ModuleName\Controller;
use Magento\Framework\App\RequestInterface;
class Sendemail extends \Magento\Framework\App\Action\Action
{
/**
* #var \Magento\Framework\App\Request\Http
*/
protected $_request;
/**
* #var \Magento\Framework\Mail\Template\TransportBuilder
*/
protected $_transportBuilder;
/**
* #var \Magento\Store\Model\StoreManagerInterface
*/
protected $_storeManager;
public function __construct(
\Magento\Framework\App\Action\Context $context
, \Magento\Framework\App\Request\Http $request
, \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder
, \Magento\Store\Model\StoreManagerInterface $storeManager
)
{
$this->_request = $request;
$this->_transportBuilder = $transportBuilder;
$this->_storeManager = $storeManager;
parent::__construct($context);
}
public function execute()
{
$store = $this->_storeManager->getStore()->getId();
$transport = $this->_transportBuilder->setTemplateIdentifier('modulename_test_template')
->setTemplateOptions(['area' => 'frontend', 'store' => $store])
->setTemplateVars(
[
'store' => $this->_storeManager->getStore(),
]
)
->setFrom('general')
// you can config general email address in Store -> Configuration -> General -> Store Email Addresses
->addTo('customer#email.com', 'Customer Name')
->getTransport();
$transport->sendMessage();
return $this;
}
}
check the flow,you create new contact form,then you can get the form data from HTTPREquestInterface,then reference the above answer for sending email,you can then send the email with custom form
public function execute()
{
$store = $this->_storeManager->getStore()->getId();
$transport = $this->_transportBuilder->setTemplateIdentifier('modulename_test_template')
->setTemplateOptions(['area' => 'frontend', 'store' => $store])
->setTemplateVars(
[
'store' => $this->_storeManager->getStore(),
]
)
->setFrom('general')
// you can config general email address in Store -> Configuration -> General -> Store Email Addresses
->addTo('customer#email.com', 'Customer Name')
->getTransport();
$transport->sendMessage();
return $this;
}
//customer#email.com---can get it from httpRequest
you can use the following method given here to send custom Emails programmatically in Magento 2.
<?php
namespace [Vendor]\[Module]\Helper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\Translate\Inline\StateInterface;
use Magento\Store\Model\StoreManagerInterface;
class Data extends AbstractHelper
{
protected $transportBuilder;
protected $storeManager;
protected $inlineTranslation;
public function __construct(
Context $context,
TransportBuilder $transportBuilder,
StoreManagerInterface $storeManager,
StateInterface $state
)
{
$this->transportBuilder = $transportBuilder;
$this->storeManager = $storeManager;
$this->inlineTranslation = $state;
parent::__construct($context);
}
public function sendEmail()
{
// this is an example and you can change template id,fromEmail,toEmail,etc as per your need.
$templateId = 'my_custom_email_template'; // template id
$fromEmail = 'owner#domain.com'; // sender Email id
$fromName = 'Admin'; // sender Name
$toEmail = 'customer#email.com'; // receiver email id
try {
// template variables pass here
$templateVars = [
'msg' => 'test',
'msg1' => 'test1'
];
$storeId = $this->storeManager->getStore()->getId();
$from = ['email' => $fromEmail, 'name' => $fromName];
$this->inlineTranslation->suspend();
$storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE;
$templateOptions = [
'area' => \Magento\Framework\App\Area::AREA_FRONTEND,
'store' => $storeId
];
$transport = $this->transportBuilder->setTemplateIdentifier($templateId, $storeScope)
->setTemplateOptions($templateOptions)
->setTemplateVars($templateVars)
->setFrom($from)
->addTo($toEmail)
->getTransport();
$transport->sendMessage();
$this->inlineTranslation->resume();
} catch (\Exception $e) {
$this->_logger->info($e->getMessage());
}
}
}
Please First install SMTP marketplace module and set mail after applying code
link :https://github.com/mageplaza/magento-2-smtp

get settings in validator - typo3

I have an extension with backend configuration options.I need to validate a phone number in AddAction and UpdateAction.I can configure the phone number format in backend (say us phone number/indian phone number etc).How can i get the settings in validator?
I have a custom validator to validate phone numbers.Here is my code
<?php
namespace vendor\Validation\Validator;
class UsphonenumberValidator extends \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
{
protected $supportedOptions = array(
'pattern' => '/^([\(]{1}[0-9]{3}[\)]{1}[ ]{1}[0-9]{3}[\-]{1}[0-9]{4})$/'
);
public function isValid($property) {
$settings = $this->settings['phone'];
$pattern = $this->supportedOptions['pattern'];
$match = preg_match($pattern, $property);
if ($match >= 1) {
return TRUE;
} else {
$this->addError('Phone number you are entered is not valid.', 1451318887);
return FALSE;
}
}
}
$settings returns null
In cases where the extbase configuration of your extension isn't implemented by default you should retrieve it yourself by using the \TYPO3\CMS\Extbase\Configuration\ConfigurationManager.
Here is an example how you can obtain the settings of your extension:
<?php
namespace MyVendor\MyExtName\Something;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Object\ObjectManager;
class Something {
/**
* #var string
*/
static protected $extensionName = 'MyExtName';
/**
* #var null|array
*/
protected $settings = NULL;
/**
* Gets the Settings
*
* #return array
*/
public function getSettings() {
if (is_null($this->settings)) {
$this->settings = [];
/* #var $objectManager \TYPO3\CMS\Extbase\Object\ObjectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/* #var $configurationManager \TYPO3\CMS\Extbase\Configuration\ConfigurationManager */
$configurationManager = $objectManager->get(ConfigurationManager::class);
$this->settings = $configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
self::$extensionName
);
}
return $this->settings;
}
}
I recommend that you implement such functionality in general. So you could retrieve any configuration of any extension as a Service inside your extension or something similar to this.
Good luck!

ACL Ressource (Controller)

i just implemented ACL in my Zend Framework which already uses Zend Auth.
I want to give access to some controllers and tried it this way:
$roleGuest = new Zend_Acl_Role('guest');
$this->addRole($roleGuest);
$this->addRole(new Zend_Acl_Role('supplier'));
$this->addRole(new Zend_Acl_Role('admin'));
$this->add(new Zend_Acl_Resource('Articles'));
$this->add(new Zend_Acl_Resource('Index'));
$this->deny();
$this->allow('supplier', 'Articles');
$this->allow('admin', null);
But a user, who is supplier (he is really :)) is not able to see the Controller Articles.
What am I doing wrong?
Thanks for help.
BR frgtv10
I think the best solution is to create a plugin and write something like this
class Application_Controller_Plugin_AclManager extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $Request)
{
$AclManager = new Zend_Acl();
$AclManager->addRole(new Zend_Acl_Role('Guest'));
$AclManager->addRole(new Zend_Acl_Role('Supplier'), 'Guest');
$AclManager->addResource(new Zend_Acl_Resource('controller1'));
$AclManager->addResource(new Zend_Acl_Resource('controller2'));
$AclManager->addResource(new Zend_Acl_Resource('controller3'));
$AclManager->allow('Guest', 'controller1', 'index');
$AclManager->allow('Supplier', 'controller2');
$AclManager->allow('Supplier', 'controller3');
It will work great. In addition you can write
if (! $AclManager->isAllowed(USER_ROLE, $Request->getControllerName(), $Request->getActionName()))
{
$this->getResponse()->setRedirect(SOME_URL_TO_REDIRECT);
}
The approach from user707795 is good. I build up my resources with Pike_Reflection_Resource to automaticly define your resources. It's not perfectly documented yet but usage is very simple:
You download the latest version of the Pike library
http://code.google.com/p/php-pike/
Then you create a ACL class which extends Zend_Acl:
<?php
class Application_Acl extends Zend_Acl
{
/**
* Constructor
*/
public function __construct()
{
$this->_addRoles();
$this->_addResources();
$this->_setAuthorization();
}
/**
* Adds roles to ACL
*/
protected function _addRoles()
{
/**
* Get your roles from the application config here or the database like below (Doctrine2)
*/
// $repository = $this->_em->getRepository('BestBuy\Entity\Usergroup');
$roles = array('guest', 'admin', 'moderator');
foreach($roles as $role) {
$this->addRole(new Zend_Acl_Role(strtolower($role)));
}
}
/**
* Adds resources to ACL
*
* Here are resources added to the ACL. You don't have to do this manually
* because Pike_Reflection_Resource will search automaticly in your controller
* directories to define which actions there are and adds every resource as:
* modulename_controller_actionname all lowercase.
*/
public function _addResources()
{
$resourceReflection = new Pike_Reflection_Resource();
$resources = $resourceReflection->toFlatArray('default');
foreach ($resources as $resource => $humanValue) {
$this->addResource(new Zend_Acl_Resource($resource));
}
}
/**
* Sets authorization
*/
public function _setAuthorization()
{
//$permissions = $this->_em->getRepository('BestBuy\Entity\Permission')->findAll();
/**
* I retrieve my permissions here from the database but you could receive the
* from the roles attribute too:
*/
$resourceReflection = new Pike_Reflection_Resource();
$resources = $resourceReflection->toArray('default');
foreach ($resources as $moduleName => $controllers) {
foreach($controllers as $controllerName=>$actions) {
foreach($actions as $actionName=>$action) {
$resourceName = sprintf('%s_%s_%s',
strtolower($moduleName),
strtolower($controllerName),
strtolower($actionName)
);
if(isset($action['roles'])) {
foreach($action['roles'] as $role) {
if ($this->hasRole($role) && $this->has($resourceName)) {
$this->allow($role, $resourceName);
}
}
}
}
}
}
}
}
?>
Then you set up a frontcontroller plugin something just like above:
<?php
class Application_Controller_Plugin_Authorization extends Zend_Controller_Plugin_Abstract
{
/**
* Request
*
* #var Zend_Controller_Request_Abstract
*/
protected $_request;
/**
* ACL
*
* #var Buza_Acl
*/
protected $_acl;
/**
* Called before Zend_Controller_Front enters its dispatch loop.
*
* #param Zend_Controller_Request_Abstract $request
* #return void
*/
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$this->_request = $request;
$this->_acl = new Application_Acl();
Zend_Registry::set('acl', $this->_acl);
$this->_checkAuthorization();
}
/**
* Checks if the current user is authorized
*/
protected function _checkAuthorization()
{
$allowed = false;
$currentResource = sprintf('%s_%s_%s',
strtolower($this->_request->getModuleName()),
strtolower($this->_request->getControllerName()),
strtolower($this->_request->getActionName())
);
if(Zend_Auth::getInstance()->hasIdentity()) {
$user = Zend_Auth::getInstance()->getIdentity());
$identityRole = strtolower($user->getRole()); //make sure you implement this function on your user class/identity!
} else {
$identityRole = 'guest';
}
if ($this->_acl->hasRole($identityRole) && $this->_acl->has($currentResource)) {
if ($this->_acl->isAllowed($identityRole, $currentResource)) {
$allowed = true;
}
}
if ($allowed !== true) {
throw new Zend_Controller_Exception('No permission', 403);
}
}
}
?>
Finally in your controller/actions you define your permissions as follows:
<?php
class IndexController extends Zend_Controller_Action {
/**
* #human Some description for the permissions of this action
* #roles guest|admin|moderator
*/
public function indexAction() {
}
/**
* #human Only for admins!
* #roles admin
*/
public function secretAction() {
}
}
?>
This approach is the best and set-up for small applications. For applications where you want to define the allowed actions in the interface of the application you should leave the roles tag and get the permissions for the database.
Please be aware that code below is not tested but with some reviewing it will work and you can control your permissions in the code.