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
Related
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 :)
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.
i have create a module and named: Custom_module by Developer Tools --> Module Builder
I trired to add "target lists" to Custom_module like module Campaign, but i can not find the way the do that
everyone can help you to find the best way to add "target lists" to my module.
thanks
You Just need to create a custom sub panel in your custom module.
target list is ProspectLists module.
Follow this link to create custom module.
http://shanedowling.com/sugarcrm-7-custom-subpanels
https://developer.sugarcrm.com/2015/05/18/creating-subpanels-with-custom-results-in-sugar-7-5/
1. Create a new link class
This should go into custom/modules//YourNewLink.php and this class will act as the custom functionality that will build your link between the two records.
<?php
/**
* Custom filtered link
*/
class YourNewLink extends Link2
{
/**
* DB
*
* #var DBManager
*/
protected $db;
public function __construct($linkName, $bean, $linkDef = false)
{
$this->focus = $bean;
$this->name = $linkName;
$this->db = DBManagerFactory::getInstance();
if (empty($linkDef)) {
$this->def = $bean->field_defs[$linkName];
} else {
$this->def = $linkDef;
}
}
/**
* Returns false if no relationship was found for this link
*
* #return bool
*/
public function loadedSuccesfully()
{
// this link always loads successfully
return true;
}
/**
* #see Link2::getRelatedModuleName()
*/
public function getRelatedModuleName()
{
return '<Your_Module>';
}
/**
*
* #see Link2::buildJoinSugarQuery()
*/
public function buildJoinSugarQuery($sugar_query, $options = array())
{
$joinParams = array('joinType' => isset($options['joinType']) ? $options['joinType'] : 'INNER');
$jta = 'active_other_invites';
if (!empty($options['joinTableAlias'])) {
$jta = $joinParams['alias'] = $options['joinTableAlias'];
}
$sugar_query->joinRaw($this->getCustomJoin($options), $joinParams);
return $sugar_query->join[$jta];
}
/**
* Builds main join subpanel
* #param string $params
* #return string JOIN clause
*/
protected function getCustomJoin($params = array())
{
$bean_id = $this->db->quoted($this->focus->id);
$sql = " INNER JOIN(";
$sql .= "SELECT id FROM accounts WHERE id={$bean_id}"; // This is essentially a select statement that will return a set of ids that you can match with the existing sugar_query
$sql .= ") accounts_result ON accounts_result.id = sugar_query_table.id";
return $sql;
}
2. Add a new vardef entry for the link field.
For this example, I'm going to create the custom link on the contacts module. So this code goes in custom/Extension/modules/Contacts/Ext/Vardefs/your_field_name.php
<?php
$dictionary["Contact"]["fields"]["your_field_name"] = array(
'name' => 'active_other_invites',
'type' => 'link',
'link_file' => 'custom/modules/<YourModule>/YourNewLink.php',
'link_class' => 'YourNewLink',
'source' => 'non-db',
'vname' => 'LBL_NEW_LINK',
'module' => '<YourModule>',
'link_type' => 'many',
'relationship' => '',
);
3. Add the new link as a subpanel
This goes under custom/Extension/modules/Contacts/Ext/clients/base/layouts/subpanels/your_subpanel_name.php
<?php
$viewdefs['Contacts']['base']['layout']['subpanels']['components'][] = array (
'layout' => 'subpanel',
'label' => 'LBL_NEW_LINK',
'context' =>
array (
'link' => 'your_field_name',
),
);
4. Add the label
Under custom/Extension/modules/Contacts/Ext/Language/en_us.new_link.php
<?php
$mod_strings['LBL_ACTIVE_OTHER_INVITES'] = 'Your New Link';
5. Quick Repair and Rebuild
When user sign up, system send a confirmation email to user its work good but without any email confirmation system automatic login or user can login. How can i solve this, that user should confirm email before login and if user not confirmed an email user can't be login?
i am using this project:
Yii 2 Advanced Template With Rbac User Managment
my LoginForm model code
namespace common\models;
use Yii;
use yii\base\Model;
/**
* Login form
*/
class LoginForm extends Model
{
public $email;
public $password;
public $rememberMe = true;
protected $_user = false;
/**
* #inheritdoc
*/
public function rules()
{
return [
// username and password are both required
['email', 'filter', 'filter' => 'trim'],
[['email','password'], 'required'],
['email', 'email'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// password is validated by validatePassword()
['password', 'validatePassword','skipOnEmpty'=>false],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* #param string $attribute the attribute currently being validated
* #param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->$attribute)) {
$this->addError('email', Yii::t('messages','Incorrect password or email.'));
$this->addError('password', Yii::t('messages','Incorrect password or email.'));
}
}
}
/**
* Logs in a user using the provided username and password.
*
* #return boolean whether the user is logged in successfully
*/
public function login()
{
if ($this->validate()) {
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
} else {
return false;
}
}
/**
* Finds user by [[username]]
*
* #return User|null
*/
public function getUser()
{
if ($this->_user === false) {
$this->_user = User::findByEmail($this->email);
}
return $this->_user;
}
public function attributeLabels()
{
return [
'email' => Yii::t('app','Email'),
'password' => Yii::t('app','Password')
];
}
}
it happen because the email status inactive by default, to change this status you can go to
common/models/user.php
and change the rule function from
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_INACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_DELETED]],
];
}
to
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_INACTIVE, self::STATUS_DELETED]],
];
}
good luck
find below function in common/models/User.php
public static function findByEmail($email)
{
return static::findOne(['email'=>$email,'status'=>self::STATUS_ACTIVE]);
}
and replace it with following
public static function findByEmail($email)
{
return static::findOne(['email'=>$email,'status'=>self::STATUS_ACTIVE,'email_verification_status'=>self::EMAIL_VERIFIED]);
}
Hope this will help you
Changing status on the backend (user table) to 10 worked like charm
You need to set up SMTP for email verification but for localhost you can use the following steps.
There is a file name like 2021XXXX-162725-6907-8769.eml in the
directory of ‘#frontend/runtime/mail’.
Open it and click on the link in it, and then you got a message ‘Your
an email has been confirmed!’ in your web browser.
Now, your email is verified and you can proceed to login user.
Use sataus = 10 in user table in database it works for me.
I develop new type, but I don't know how I can test it.
Assert annotation is not load and validations is not called.
Could any one please help me?
class BarcodeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->
add('price');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Bundles\MyBundle\Form\Model\Barcode',
'intention' => 'enable_barcode',
));
}
public function getName()
{
return 'enable_barcode';
}
}
A have following model for storing form data.
namepspace Bundles\MyBundle\Form\Model;
class Barcode
{
/**
* #Assert\Range(
* min = "100",
* max = "100000",
* minMessage = "...",
* maxMessage = "..."
* )
*/
public $price;
}
I develop some test like this, the form didn't get valid data but it is valid! (Because annotation is not applied)
I try adding ValidatorExtension but I dont know how can I set constructor paramaters
function test...()
{
$field = $this->factory->createNamed('name', 'barcode');
$field->bind(
array(
'price' => 'hello',
));
$data = $field->getData();
$this->assertTrue($field->isValid()); // Must not be valid
}
Not sure why you need to unit-test the form. Cant You unit test validation of Your entity and cover controller with your expected output?
While testing validation of entity You could use something like this:
public function testIncorrectValuesOfUsernameWhileCallingValidation()
{
$v = \Symfony\Component\Validator\ValidatorFactory::buildDefault();
$validator = $v->getValidator();
$not_valid = array(
'as', '1234567890_234567890_234567890_234567890_dadadwadwad231',
"tab\t", "newline\n",
"Iñtërnâtiônàlizætiøn hasn't happened to ", 'trśżź',
'semicolon;', 'quote"', 'tick\'', 'backtick`', 'percent%', 'plus+', 'space ', 'mich #l'
);
foreach ($not_valid as $key) {
$violations = $validator->validatePropertyValue("\Brillante\SampleBundle\Entity\User", "username", $key);
$this->assertGreaterThan(0, count($violations) ,"dissalow username to be ($key)");
}
}
Functional test. Given that you generate a CRUD with app/console doctrine:generate:crud with routing=/ss/barcode, and given that maxMessage="Too high" you can:
class BarcodeControllerTest extends WebTestCase
{
public function testValidator()
{
$client = static::createClient();
$crawler = $client->request('GET', '/ss/barcode/new');
$this->assertTrue(200 === $client->getResponse()->getStatusCode());
// Fill in the form and submit it
$form = $crawler->selectButton('Create')->form(array(
'ss_bundle_eavbundle_barcodetype[price]' => '12',
));
$client->submit($form);
$crawler = $client->followRedirect();
// Check data in the show view
$this->assertTrue($crawler->filter('td:contains("12")')->count() > 0);
// Edit the entity
$crawler = $client->click($crawler->selectLink('Edit')->link());
/* force validator response: */
$form = $crawler->selectButton('Edit')->form(array(
'ss_bundle_eavbundle_barcodetype[price]' => '1002',
));
$crawler = $client->submit($form);
// Check the element contains the maxMessage:
$this->assertTrue($crawler->filter('ul li:contains("Too high")')->count() > 0);
}
}
Include this line must be in Model and try it after include look like your model.
/* Include the required validators */
use Symfony\Component\Validator\Constraints as Assert;
namespace Bundles\MyBundle\Form\Model;
class Barcode
{
/**
* #Assert\Range(
* min = "100",
* max = "100000",
* minMessage = "min message here",
* maxMessage = "max message here"
* )
*/
public $price;
}