How to attach invoice PDF instead of packing slip in magento 2 - magento2

I want to attach invoice pdf instead of packing slip while creating shipment.
I am using Fooman Email attachment extension version 2.0.8
My magento is 2.2.5 Can anyone know how can I change the attched PDF in shipping confirmation mail ?
Currently it is attaching packing slip but I want to attach invoice pdf in shipping confirmation mail.

We had a similar problem using fooman. We also wanted to send our invoice on shipment creation, while disableing the standard transactional invoice mail on invoice creation. I wrote a module that sends the invoice email together with the shipping email, which is not exactly what you are looking for but maybe you can leverage that.
The module is pretty simple. Except from boilerplate registration.php and module.xml, all you need is to override InvoiceOrder from Magento\Sales\Model and comment out this line:
$this->notifierInterface->notify($order, $invoice, $comment);
like so:
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Sales\Model;
use Magento\Framework\App\ResourceConnection;
use Magento\Sales\Api\Data\InvoiceCommentCreationInterface;
use Magento\Sales\Api\Data\InvoiceCreationArgumentsInterface;
use Magento\Sales\Api\InvoiceOrderInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order\Config as OrderConfig;
use Magento\Sales\Model\Order\Invoice\NotifierInterface;
use Magento\Sales\Model\Order\InvoiceDocumentFactory;
use Magento\Sales\Model\Order\InvoiceRepository;
use Magento\Sales\Model\Order\OrderStateResolverInterface;
use Magento\Sales\Model\Order\PaymentAdapterInterface;
use Magento\Sales\Model\Order\Validation\InvoiceOrderInterface as InvoiceOrderValidator;
use Psr\Log\LoggerInterface;
/**
* Class InvoiceOrder
* #SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class InvoiceOrder implements InvoiceOrderInterface
{
/**
* #var ResourceConnection
*/
private $resourceConnection;
/**
* #var OrderRepositoryInterface
*/
private $orderRepository;
/**
* #var InvoiceDocumentFactory
*/
private $invoiceDocumentFactory;
/**
* #var PaymentAdapterInterface
*/
private $paymentAdapter;
/**
* #var OrderStateResolverInterface
*/
private $orderStateResolver;
/**
* #var OrderConfig
*/
private $config;
/**
* #var InvoiceRepository
*/
private $invoiceRepository;
/**
* #var InvoiceOrderValidator
*/
private $invoiceOrderValidator;
/**
* #var NotifierInterface
*/
private $notifierInterface;
/**
* #var LoggerInterface
*/
private $logger;
/**
* InvoiceOrder constructor.
* #param ResourceConnection $resourceConnection
* #param OrderRepositoryInterface $orderRepository
* #param InvoiceDocumentFactory $invoiceDocumentFactory
* #param PaymentAdapterInterface $paymentAdapter
* #param OrderStateResolverInterface $orderStateResolver
* #param OrderConfig $config
* #param InvoiceRepository $invoiceRepository
* #param InvoiceOrderValidator $invoiceOrderValidator
* #param NotifierInterface $notifierInterface
* #param LoggerInterface $logger
* #SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
ResourceConnection $resourceConnection,
OrderRepositoryInterface $orderRepository,
InvoiceDocumentFactory $invoiceDocumentFactory,
PaymentAdapterInterface $paymentAdapter,
OrderStateResolverInterface $orderStateResolver,
OrderConfig $config,
InvoiceRepository $invoiceRepository,
InvoiceOrderValidator $invoiceOrderValidator,
NotifierInterface $notifierInterface,
LoggerInterface $logger
) {
$this->resourceConnection = $resourceConnection;
$this->orderRepository = $orderRepository;
$this->invoiceDocumentFactory = $invoiceDocumentFactory;
$this->paymentAdapter = $paymentAdapter;
$this->orderStateResolver = $orderStateResolver;
$this->config = $config;
$this->invoiceRepository = $invoiceRepository;
$this->invoiceOrderValidator = $invoiceOrderValidator;
$this->notifierInterface = $notifierInterface;
$this->logger = $logger;
}
/**
* #param int $orderId
* #param bool $capture
* #param array $items
* #param bool $notify
* #param bool $appendComment
* #param \Magento\Sales\Api\Data\InvoiceCommentCreationInterface|null $comment
* #param \Magento\Sales\Api\Data\InvoiceCreationArgumentsInterface|null $arguments
* #return int
* #throws \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
* #throws \Magento\Sales\Api\Exception\CouldNotInvoiceExceptionInterface
* #throws \Magento\Framework\Exception\InputException
* #throws \Magento\Framework\Exception\NoSuchEntityException
* #throws \DomainException
*/
public function execute(
$orderId,
$capture = false,
array $items = [],
$notify = false,
$appendComment = false,
InvoiceCommentCreationInterface $comment = null,
InvoiceCreationArgumentsInterface $arguments = null
) {
$connection = $this->resourceConnection->getConnection('sales');
$order = $this->orderRepository->get($orderId);
$invoice = $this->invoiceDocumentFactory->create(
$order,
$items,
$comment,
($appendComment && $notify),
$arguments
);
$errorMessages = $this->invoiceOrderValidator->validate(
$order,
$invoice,
$capture,
$items,
$notify,
$appendComment,
$comment,
$arguments
);
if ($errorMessages->hasMessages()) {
throw new \Magento\Sales\Exception\DocumentValidationException(
__("Invoice Document Validation Error(s):\n" . implode("\n", $errorMessages->getMessages()))
);
}
$connection->beginTransaction();
try {
$order = $this->paymentAdapter->pay($order, $invoice, $capture);
$order->setState(
$this->orderStateResolver->getStateForOrder($order, [OrderStateResolverInterface::IN_PROGRESS])
);
$order->setStatus($this->config->getStateDefaultStatus($order->getState()));
$invoice->setState(\Magento\Sales\Model\Order\Invoice::STATE_PAID);
$this->invoiceRepository->save($invoice);
$this->orderRepository->save($order);
$connection->commit();
} catch (\Exception $e) {
$this->logger->critical($e);
$connection->rollBack();
throw new \Magento\Sales\Exception\CouldNotInvoiceException(
__('Could not save an invoice, see error log for details')
);
}
if ($notify) {
if (!$appendComment) {
$comment = null;
}
//$this->notifierInterface->notify($order, $invoice, $comment);
}
return $invoice->getEntityId();
}
}
Now all you have to do is setup events.xml and observe sales_order_shipment_save_after
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name='sales_order_shipment_save_after'>
<observer name='SendInvoiceWithShipment' instance='Vendor\Module\Observer\SendInvoiceWithShipment'
/>
</event>
</config>
Observer leverages standard Magento 2 transactional email $this->_invoiceSender->send($invoice); like so:
<?php
namespace Vendor\Module\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Email\Sender\InvoiceSender;
class SendInvoiceWithShipment implements ObserverInterface
{
protected $_invoiceSender;
public function __construct(
InvoiceSender $invoiceSender
) {
$this->_invoiceSender = $invoiceSender;
}
public function execute(\Magento\Framework\Event\Observer $observer)
{
$order = $observer->getShipment()->getOrder();
if (!$order) {
// Dont send invoice if order is not provided
return;
}
$invoices = $order->getInvoiceCollection();
foreach ($invoices as $invoice) {
try {
$this->_invoiceSender->send($invoice);
} catch (\Exception $e) {
// Do something if failed to send
}
}
}
}

Related

How to send a scheduled email based on a date for the user using Laravel 8?

I want to send 7 days prior to the user, Theirs Selected plan is going to expire. I have implemented using command and cron jobs to run the command. But it is not working. Please anybody guide me to correct my mistake?
app/Console/Commands/AutoPlanRenewalMail.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Mail;
use App\Mail\PlanRenewalMail;
use App\User;
class AutoPlanRenewalMail extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'auto:planrenewal';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return int
*/
public function handle()
{
$users = User::whereNotNull('plan_validity')->get();
$check = true;
foreach($users as $user){
if(Carbon::parse($user->plan_validity)->diffInDays(Carbon::now()) == 7){ //Or however your date field on user is called
Mail::to($user)->send(new PlanRenewalMail($user));
}
}
return 0;
}
}
app/Console/kernal.php
<?php
namespace App\Console;
use App\Console\Commands\AutoPlanRenewalMail;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* #var array
*/
protected $commands = [
AutoPlanRenewalMail::class,
];
/**
* Define the application's command schedule.
*
* #param \Illuminate\Console\Scheduling\Schedule $schedule
* #return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
$schedule->command('auto:planrenewal')->daily();
}
/**
* Register the commands for the application.
*
* #return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}
app/Mail/PlanRenewalMail.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class PlanRenewalMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
//return $this->view('view.name');
return $this->subject("{{ ENV('APP_NAME') }} EXPIRING")
->view('emails.plan-renewal-mail');
}
}
cron jobs
* * * * * /usr/local/bin/php /home/username(changed to my username)/public_html/artisan schedule:run >> /dev/null 2>&1
then included email template blade file. But any email didn't get still. Is there any coding errors?

hydrate multiple objects zf2

I need to hyrdate multiple objests in one form. Here is what I use:
Product Form - I have a form where I call three fieldsets
Product Fieldset
Promotion Fieldset
Category Fieldset
I have Models for all the necessary tables, here is an example for the product model:
class Product implements ProductInterface
{
/**
* #var int
*/
protected $Id;
/**
* #var string
*/
protected $Title;
/**
* #var float
*/
protected $Price;
/**
* #var string
*/
protected $Description;
/**
* #var string
*/
protected $Url;
/**
* #var \DateTime
*/
protected $DateAdded;
/**
* #var string
*/
protected $Image;
/**
* #var int
*/
protected $Status;
/**
* #return int
*/
public function getId()
{
return $this->Id;
}
/**
* #param int $Id
*/
public function setId($Id)
{
$this->Id = $Id;
}
/**
* #return string
*/
public function getTitle()
{
return $this->Title;
}
/**
* #param string $Title
*/
public function setTitle($Title)
{
$this->Title = $Title;
}
/**
* #return float
*/
public function getPrice()
{
return $this->Price;
}
/**
* #param float $Price
*/
public function setPrice($Price)
{
$this->Price = $Price;
}
/**
* #return string
*/
public function getDescription()
{
return $this->Description;
}
/**
* #param string $Description
*/
public function setDescription($Description)
{
$this->Description = $Description;
}
/**
* #return string
*/
public function getUrl()
{
return $this->Url;
}
/**
* #param string $Url
*/
public function setUrl($Url)
{
$this->Url = $Url;
}
/**
* #return \DateTime
*/
public function getDateAdded()
{
return $this->DateAdded;
}
/**
* #param \DateTime $DateAdded
*/
public function setDateAdded($DateAdded)
{
$this->DateAdded = $DateAdded;
}
/**
* #return string
*/
public function getImage()
{
return $this->Image;
}
/**
* #param string $Image
*/
public function setImage($Image)
{
$this->Image = $Image;
}
/**
* #return int
*/
public function getStatus()
{
return $this->Status;
}
/**
* #param int $Status
*/
public function setStatus($Status)
{
$this->Status = $Status;
}
In my controllers I want to bind the data to my view so I can edit them.
try {
$aProduct = $this->productService->findProduct($iId);
} catch (\Exception $ex) {
// ...
}
$form = new ProductForm();
$form->bind($aProduct);
In the first place I need to select all the necessary information from the DB. I join three tables product, promotion and category tables. I must return the data to my controller as objects and bind them in my form to be able to edit on the view page.
Please give me some ideas how to accomplish this so I can continue with my development. I am stuck.
I will appreciate all the links which can help me or give me any ideas/examples from the real life.
public function findProduct($Id)
{
$iId = (int) $Id;
$sql = new Sql($this->dbAdapter);
$select = $sql->select('product');
$select->join('promotion', 'promotion.ProductId = product.Id', array('Discount', 'StartDate', 'EndDate', 'PromotionDescription' => 'Description', 'PromotionStatus', 'Type'), 'left');
$select->join('producttocategory', 'producttocategory.ProductId = product.Id', array('CategoryId'), 'left');
$select->join('category', 'category.Id = producttocategory.CategoryId', array('ParentId', 'Title', 'Description', 'Url', 'DateAdded', 'Image', 'Status'), 'left');
$where = new Where();
$where->equalTo('product.Id', $iId);
$select->where($where);
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet($this->hydrator, $this->productPrototype);
return $resultSet->initialize($result);
}
throw new \Exception("Could not find row $Id");
}
I need to hydrate the result and return an object which I will use in the controller to bind the form.
You can to fill entities from a database manually.
If you want to fill automatically need to create a map between a database and entities. I made a library for making a map between DB and entities use annotations in entities https://github.com/newage/annotations.
Next step.
When you get different data from tables. Example:
SELECT
table1.id AS table1.id,
table1.title AS table1.title,
table2.id AS table2.id,
table2.alias AS table2.alias
FROM table1
JOIN table2 ON table1.id = table2.id
Need do foreach by rows and set data to entities comparing row with table name and Entity from a generated map.
Auto generating tree of entities from DB is my next project.
But it's do not finished. https://github.com/newage/zf2-simple-orm.

Symfony 3 Testing - "AlreadySubmittedException" on form testing

I have some problems for testing a creation form (for a simple Bank entity) in a SF3 project. Here the test code:
class BankControllerTest extends WebTestCase
{
/**
* Test the creation form action
*/
function testNewAction()
{
$client = static::createClient();
// User must be logged in
$client->request('GET', '/bank/bank/new');
$response = $client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
// Page request test
$client = LoginControllerTest::createClientConnectedTest();
$crawler = $client->request('GET', '/bank/bank/new');
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
// Form testing
$formData = array(
'bank[name]' => 'BankTest '.uniqid(),
'bank[comment]' => 'Tast bank '.uniqid().' comment.',
);
$submitButtonCrawlerNode = $crawler->selectButton('Create');
$form = $submitButtonCrawlerNode->form();
$client->submit($form, $formData);
$response = $client->getResponse();
$container = $client->getContainer();
$this->assertEquals(301, $response->getStatusCode());
}
}
The part which interesting me is the last "Form testing" part, I pasted everything in case of I made a mistake before.
Instead of the "301" httpCode I want, I have a 500 error :
vendor/bin/phpunit src/BillBrother/BankAccountBundle
[...]
1) BillBrother\BankAccountBundle\Test\Controller\BankControllerTest::testNewAction
Failed asserting that 500 matches expected 301.
/home/developer/src/BillBrother/BankAccountBundle/Test/Controller/BankControllerTest.php:47
And saw it is an "AlreadySubmittedException" which generate this error :
[2016-06-30 08:00:18] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Form\Exception\AlreadySubmittedException: "You cannot add children to a submitted form" at /home/developer/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php line 802 {"exception":"[object] (Symfony\\Component\\Form\\Exception\\AlreadySubmittedException(code: 0): You cannot add children to a submitted form at /home/developer/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php:802)"} []
Did I do something in a wrong way?
Thanks in advance for your help.
Here some other parts of code I used in this test:
The Login test file, from where I use the createClientConnectedTest() static function :
/**
* BillBrother\AuthBundle\Controller test class file.
*
* #TODO Write tests
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\AuthBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
/**
* BillBrother\AuthBundle\Controller test class.
*/
class LoginControllerTest extends WebTestCase
{
/**
* Create a connected client.
*
* #param string $login
* #param string $password
* #return \Symfony\Bundle\FrameworkBundle\Client
*/
public static function createClientConnected($login, $password)
{
return static::createClient(array(), array(
'PHP_AUTH_USER' => $login,
'PHP_AUTH_PW' => $password
));
}
/**
* Create a client connected with test account
*
* #return \Symfony\Bundle\FrameworkBundle\Client
*/
public static function createClientConnectedTest()
{
return static::createClientConnected(
'test#email.com',
'password'
);
}
/**
* Test the login form
*/
public function testLoginform()
{
$client = static::createClient();
$crawler = $client->request('GET', '/login');
}
}
The BankType class :
/**
* Bank entity type
*
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\BankAccountBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
/**
* Bank type form
*/
class BankType extends AbstractType
{
/**
* Build the form
*
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('comment', TextareaType::class)
;
}
/**
* Configure the form default options.
*
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BillBrother\BankAccountBundle\Entity\Bank'
));
}
}
The Bank entity class :
/**
* BillBrother bank entity class file.
*
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\BankAccountBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use BillBrother\AuthBundle\Entity\User;
use Datetime;
/**
* Bank
*
* #ORM\Table(name="bank")
* #ORM\Entity(repositoryClass="BillBrother\BankAccountBundle\Repository\BankRepository")
*/
class Bank
{
/**
* The bank id.
*
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* Bank creator
*
* The user account who created the bank.
*
* If null, it means the bank was created by the system.
*
* #var User
*
* #ORM\JoinColumn(name="creator_id", nullable=true)
* #ORM\ManyToOne(targetEntity="BillBrother\AuthBundle\Entity\User")
*/
private $creator;
/**
* The bank name.
*
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Comments about the bank.
*
* #var string
*
* #ORM\Column(name="comment", type="text")
*/
private $comment;
/**
* Bank row creation date
*
* #var Datetime
*
* #ORM\Column(name="create_date", type="datetime")
*/
private $createDate;
/**
* Constuctor
*
* Initialize the creation date to the current date
*/
public function __construct()
{
$this->createDate = new Datetime;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name, nullable=tru
* #return Bank
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set comment
*
* #param string $comment
*
* #return Bank
*/
public function setComment($comment)
{
$this->comment = $comment;
return $this;
}
/**
* Get comment
*
* #return string
*/
public function getComment()
{
return $this->comment;
}
/**
* Set creator
*
* #param User $creator
*
* #return Bank
*/
public function setCreator(User $creator = null)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* #return User
*/
public function getCreator()
{
return $this->creator;
}
}
The controller called :
/**
* BillBrother bank account bundle bank controller class file.
*
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\BankAccountBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use BillBrother\BankAccountBundle\Entity\Bank;
use BillBrother\BankAccountBundle\Form\Type\BankType;
/**
* BillBrother application bank account bank controller.
*/
class BankController extends Controller
{
/**
*/
private function _buildForm(Request $request, Bank $bank)
{
$form = $this->createForm(BankType::class, $bank);
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($bank);
$em->flush();
}
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
return $form;
}
/**
* Bank account bundle bank list page action.
*
* #Route("/banks", name="billbrother_bankaccount_bank_list")
*/
public function listAction()
{
return $this->render('BillBrotherBankAccountBundle:Bank:list.html.twig');
}
/**
* Create a new bank page action.
*
* #param Request $request
*
* #Route("/bank/new", name="billbrother_bankaccount_bank_new")
*/
public function newAction(Request $request)
{
$bank = new Bank();
$form = $this->_buildForm($request, $bank);
if($form->isValid())
return $this->redirectToRoute(
'billbrother_bankaccount_bank_edit',
array('id'=>$bank->getId())
);
return $this->render(
'BillBrotherBankAccountBundle:Bank:form.html.twig',
array(
'form' => $form->createView()
)
);
}
/**
* Modify a bank page action.
*
* #Route("/bank/edit/{id}", name="billbrother_bankaccount_bank_edit")
*/
public function editAction($id)
{
return $this->render('BillBrotherBankAccountBundle:Bank:form.html.twig');
}
}
And about versions :
✗ bin/console --version
Symfony version 3.1.1 - app/dev/debug
✗ vendor/bin/phpunit --version
PHPUnit 5.4.6 by Sebastian Bergmann and contributors.
✗ php --version
PHP 7.0.6 (cli) (built: May 24 2016 03:29:56) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
I saw where my problem was from. In my Controller :
private function _buildForm(Request $request, Bank $bank)
{
$form = $this->createForm(BankType::class, $bank);
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($bank);
$em->flush();
}
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
return $form;
}
We see I add my "submit" button after the form handleRequest(). By moving the code :
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
just after my form creation, everything is ok.
The final function code :
private function _buildForm(Request $request, Bank $bank)
{
$form = $this->createForm(BankType::class, $bank);
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($bank);
$em->flush();
}
return $form;
}

How to use Catalog rule "Conditions_serialized" to validate product attribute?

I have define a condition the which product as product attribute "Hot" equal to "yes" on my table "product_label".
How to use \Magento\CatalogRule\Model\Rule\Condition... to validate $product with my condition?
The condition is record as following
a:7:{s:4:"type";s:48:"Magento\CatalogRule\Model\Rule\Condition
\Combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_
processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:
{s:4:"type";s:48:"Magento\CatalogRule\Model\Rule\Condition
\Product";s:9:"attribute";s:7:"hkt_hot";s:8:"operator";s:2:"==";s:5:"value";
s:1:"1";s:18:"is_value_processed";b:0;}}}
Thanks
Norman
Without coding you can make this check using the system already in place:
Your "hot" attribute property is_used_for_promo_rules must be set to 1.
Additionally, the function Mage_Catalog_Model_Resource_Eav_Attribute::isAllowedForRuleCondition() sets the following prerequisites:
An attribute must be visible (attribute property is_visible must be equal 1).
The frontend_input property must be one of the following types: ‘text’, ‘multiselect’, ‘textarea’, ‘date’, ‘datetime’, ‘select’, ‘boolean’, ‘price’.
You may find the list of product IDs satisfying the serialized conditions.
Create model Vendor/Module/Model/Rule.php
<?php
namespace Vendor\Module\Model;
class Rule extends \Magento\CatalogRule\Model\Rule
{
/**
* Prefix of model events names
*
* #var string
*/
protected $_eventPrefix = 'catalogrule_rule';
/**
* Parameter name in event
*
* In observe method you can use $observer->getEvent()->getRule() in this case
*
* #var string
*/
protected $_eventObject = 'rule';
/**
* Store matched product Ids
*
* #var array
*/
protected $_productIds;
/**
* Limitation for products collection
*
* #var int|array|null
*/
protected $_productsFilter = null;
/**
* Store current date at "Y-m-d H:i:s" format
*
* #var string
*/
protected $_now;
/**
* Cached data of prices calculated by price rules
*
* #var array
*/
protected static $_priceRulesData = [];
/**
* Catalog rule data
*
* #var \Magento\CatalogRule\Helper\Data
*/
protected $_catalogRuleData;
/**
*
* #var \Magento\Framework\App\Cache\TypeListInterface
*/
protected $_cacheTypesList;
/**
*
* #var array
*/
protected $_relatedCacheTypes;
/**
*
* #var \Magento\Framework\Stdlib\DateTime
*/
protected $dateTime;
/**
*
* #var \Magento\Framework\Model\ResourceModel\Iterator
*/
protected $_resourceIterator;
/**
*
* #var \Magento\Customer\Model\Session
*/
protected $_customerSession;
/**
*
* #var \Magento\CatalogRule\Model\Rule\Condition\CombineFactory
*/
protected $_combineFactory;
/**
*
* #var \Magento\CatalogRule\Model\Rule\Action\CollectionFactory
*/
protected $_actionCollectionFactory;
/**
*
* #var \Magento\Catalog\Model\ProductFactory
*/
protected $_productFactory;
/**
*
* #var \Magento\Store\Model\StoreManagerInterface
*/
protected $_storeManager;
/**
*
* #var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
*/
protected $_productCollectionFactory;
/**
*
* #var \Magento\CatalogRule\Model\Indexer\Rule\RuleProductProcessor;
*/
protected $_ruleProductProcessor;
/**
*
* #var Data\Condition\Converter
*/
protected $ruleConditionConverter;
/**
* Rule constructor.
*
* #param \Magento\Framework\Model\Context $context
* #param \Magento\Framework\Registry $registry
* #param \Magento\Framework\Data\FormFactory $formFactory
* #param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* #param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
* #param \Magento\Store\Model\StoreManagerInterface $storeManager
* #param Rule\Condition\CombineFactory $combineFactory
* #param Rule\Action\CollectionFactory $actionCollectionFactory
* #param \Magento\Catalog\Model\ProductFactory $productFactory
* #param \Magento\Framework\Model\ResourceModel\Iterator $resourceIterator
* #param \Magento\Customer\Model\Session $customerSession
* #param \Magento\CatalogRule\Helper\Data $catalogRuleData
* #param \Magento\Framework\App\Cache\TypeListInterface $cacheTypesList
* #param \Magento\Framework\Stdlib\DateTime $dateTime
* #param Indexer\Rule\RuleProductProcessor $ruleProductProcessor
* #param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
* #param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
* #param array $relatedCacheTypes
* #param array $data
*
* #SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(\Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Data\FormFactory $formFactory, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\CatalogRule\Model\Rule\Condition\CombineFactory $combineFactory, \Magento\CatalogRule\Model\Rule\Action\CollectionFactory $actionCollectionFactory, \Magento\Catalog\Model\ProductFactory $productFactory, \Magento\Framework\Model\ResourceModel\Iterator $resourceIterator, \Magento\Customer\Model\Session $customerSession, \Magento\CatalogRule\Helper\Data $catalogRuleData, \Magento\Framework\App\Cache\TypeListInterface $cacheTypesList, \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\CatalogRule\Model\Indexer\Rule\RuleProductProcessor $ruleProductProcessor, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $relatedCacheTypes = [], array $data = [])
{
parent::__construct($context, $registry, $formFactory, $localeDate, $productCollectionFactory, $storeManager, $combineFactory, $actionCollectionFactory, $productFactory, $resourceIterator, $customerSession, $catalogRuleData, $cacheTypesList, $dateTime, $ruleProductProcessor, $resource, $resourceCollection, $relatedCacheTypes, $data);
}
/**
* Init resource model and id field
*
* #return void
*/
protected function _construct()
{
parent::_construct();
$this->_init('Magento\CatalogRule\Model\ResourceModel\Rule');
$this->setIdFieldName('rule_id');
}
/**
* Getter for rule conditions collection
*
* #return \Magento\Rule\Model\Condition\Combine
*/
public function getConditionsInstance()
{
return $this->_combineFactory->create();
}
/**
* Getter for rule actions collection
*
* #return \Magento\CatalogRule\Model\Rule\Action\Collection
*/
public function getActionsInstance()
{
return $this->_actionCollectionFactory->create();
}
public function toArray(array $arrAttributes = array())
{
return parent::toArray($arrAttributes);
}
public function getListProductIds()
{
$productCollection = \Magento\Framework\App\ObjectManager::getInstance()->create('\Magento\Catalog\Model\ResourceModel\Product\Collection');
$productFactory = \Magento\Framework\App\ObjectManager::getInstance()->create('\Magento\Catalog\Model\ProductFactory');
$this->_productIds = [];
$this->setCollectedAttributes([]);
$this->getConditions()->collectValidatedAttributes($productCollection);
\Magento\Framework\App\ObjectManager::getInstance()->create('\Magento\Framework\Model\ResourceModel\Iterator')->walk($productCollection->getSelect(), [
[
$this,
'callbackValidateProduct'
]
], [
'attributes' => $this->getCollectedAttributes(),
'product' => $productFactory->create()
]);
return $this->_productIds;
}
/**
* Callback function for product matching
*
* #param array $args
* #return void
*/
public function callbackValidateProduct($args)
{
$product = clone $args['product'];
$product->setData($args['row']);
$websites = $this->_getWebsitesMap();
foreach ($websites as $websiteId => $defaultStoreId) {
$product->setStoreId($defaultStoreId);
if ($this->getConditions()->validate($product)) {
$this->_productIds[] = $product->getId();
}
}
}
/**
* Prepare website map
*
* #return array
*/
protected function _getWebsitesMap()
{
$map = [];
$websites = \Magento\Framework\App\ObjectManager::getInstance()->create('\Magento\Store\Model\StoreManagerInterface')->getWebsites();
foreach ($websites as $website) {
// Continue if website has no store to be able to create catalog rule for website without store
if ($website->getDefaultStore() === null) {
continue;
}
$map[$website->getId()] = $website->getDefaultStore()->getId();
}
return $map;
}
}
Then, wherever you'd like to find the product IDs. Use below code snippet.
...
private $ruleModel;
...
...
private function getRuleModel()
{
if($this->ruleModel === null) {
$this->ruleModel = $this->objectManager->create('Vendor\Module\Model\Rule');
$this->ruleModel->setName('name')
->setDescription('description')
->setWebsiteIds($this->objectManager->get('Magento\Store\Model\StoreManagerInterface')->getStore()->getWebsiteId());
}
return $this->ruleModel;
}
...
...
// somewhere in your method
$matchedProductIds = $this->getRuleModel()
->setData('conditions_serialized', $conditions_serialized)->getListProductIds();
...

Symfony2 - How to call Swiftmailer service in the controller

I'm still learning on how to setup Swiftmailer as a service, I believe I have a working solution but need some help on how to call this in the controller.
How do I call this service in my controller? (service code, original code before service and service.yml below)
Edit:
I am trying to call it like so:
$emailManager = $this->container->get('email_manager');
$content = $emailManager->sendMail($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText);
But am getting a undefined variable error:
Notice: Undefined variable: subject in /.../DefaultController.php line 58
EmailManager service
namespace Acme\EmailBundle\Service;
use Symfony\Component\HttpFoundation\RequestStack;
class EmailManager
{
private $request;
private $mailer;
public function __construct(RequestStack $requestStack, \Swift_Mailer $mailer)
{
$this->request = $requestStack->getCurrentRequest();
$this->mailer = $mailer;
}
public function sendMail($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText)
{
/* #var $mailer \Swift_Mailer */
if(!$this->mailer->getTransport()->isStarted()){
$this->mailer->getTransport()->start();
}
/* #var $message \Swift_Message */
$message = $this->mailer->createMessage();
$message->setSubject($subject);
$message->setBody($bodyHtml, 'text/html');
$message->addPart($bodyText, 'text/plain', 'UTF8');
$message->addTo($recipientEmail, $recipientName);
$message->setFrom( array('example#gmail.com' => 'Chance') );
$this->mailer->send($message);
$this->mailer->getTransport()->stop();
}
}
Original controller code for sending emails prior to putting it in as a service
/**
* #Route("/", name="contact")
* #Template("AcmeEmailBundle:Default:index.html.twig")
*/
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
if ($request->isMethod('POST')) {
$form->submit($request);
if ($form->isValid()) {
$message = \Swift_Message::newInstance()
->setSubject($form->get('subject')->getData())
->setFrom($form->get('email')->getData())
->setTo('example#gmail.com')
->setBody(
$this->renderView(
'AcmeEmailBundle:Default:index.html.twig',
array(
'ip' => $request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
)
)
);
$this->get('mailer')->send($message);
$request->getSession()->getFlashBag()->add('success', 'Your email has been sent! Thanks!');
return $this->redirect($this->generateUrl('contact'));
}
}
return array(
'form' => $form->createView()
);
}
services.yml
services:
email_manager:
class: Acme\EmailBundle\Service\EmailManager
arguments: [#request_stack, #mailer]
scope: request
When your controller extends Controller like so
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
//...
/**
* DemoController
*/
class DemoController extends Controller
{
// ...
}
You can access the services like this:
$emailManager = $this->container->get('email_manager');
You can then send your email like this:
$emailManager->sendEmail($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText);
Complete Overview
1 Create an Email Manager that will compose your emails and send them
<?php
namespace Acme\EmailBundle\Manager;
//...
/**
* Composes and Sends emails
*/
class EmailManager
{
/**
* The mailer
*
* #var \Swift_Mailer
*/
protected $mailer;
/**
* The email address the mailer will send the emails from
*
* #var String
*/
protected $emailFrom;
/**
* #param Request $mailer;
*/
public function __construct(\Swift_Mailer $mailer, $emailFrom)
{
$this->mailer = $mailer;
$this->emailFrom = $emailFrom;
}
/**
* Compose email
*
* #param String $subject
* #param String $recipientEmail
* #param String $bodyHtml
* #return \Swift_Message
*/
public function composeEmail($subject, $recipientEmail, $bodyHtml)
{
/* #var $message \Swift_Message */
$message = $this->mailer->createMessage();
$message->setSubject($subject)
->setBody($bodyHtml, 'text/html')
->setTo($recipientEmail)
->setFrom($this->emailFrom);
return $message;
}
/**
* Send email
*
* #param \Swift_Message $message;
*/
public function sendEmail(\Swift_Message $message)
{
if(!$this->mailer->getTransport()->isStarted()){
$this->mailer->getTransport()->start();
}
$this->mailer->send($message);
$this->mailer->getTransport()->stop();
}
}
3 Declare it as a service
parameters:
acme_email.email_from: example#gmail.com
services:
email_manager:
class: Acme\EmailBundle\Service\EmailManager
arguments: [#mailer,%acme_email.email_from%]
]
4 Create a Form Handler that will handle your contact forms
<?php
namespace Acme\ContactBundle\Form\Handler;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Acme\EmailBundle\Manager\EmailManager;
/**
* Handles Contact forms
*/
class ContactFormHandler
{
/**
* The request
*
* #var Symfony\Component\HttpFoundation\Request;
*/
protected $request;
/**
* The Template Engine
*/
protected $templating;
/**
* The email manager
*/
protected $emailManager;
/**
* #param Request $request;
* #param TwigEngine $templating
* #param EmailManager $emailManager
*/
public function __construct(Request $request, TwigEngine $templating, EmailManager $emailManager)
{
$this->request = $request;
$this->templating =$templating;
$this->emailManager = $emailManager;
}
/**
* Processes the form with the request
*
* #param Form $form
* #return Email|false
*/
public function process(Form $form)
{
if ('POST' !== $this->request->getMethod()) {
return false;
}
$form->bind($this->request);
if ($form->isValid()) {
return $this->processValidForm($form);
}
return false;
}
/**
* Processes the valid form, sends the email
*
* #param Form
* #return EmailInterface The email sent
*/
public function processValidForm(Form $form)
{
/** #var EmailInterface */
$email = $this->composeEmail($form);
/** Send Email */
$this->emailManager->sendEmail($email);
return $email;
}
/**
* Composes the email from the form
*
* #param Form $form
* #return \Swift_Message
*/
public function composeEmail(Form $form)
{
$subject = $form->get('subject')->getData();
$recipientEmail = $form->get('email')->getData();
$bodyHTML = $this->templating->renderView(
'AcmeEmailBundle:Default:index.html.twig',
array(
'ip' => $this->request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
)
);
/** #var \Swift_Message */
return $this->emailManager->composeEmail($subject, $recipientEmail, $bodyHTML);
}
}
3 Declare it as a service:
services:
acme_contact.contact_form_handler:
class: Acme\ContactBundle\FormHandler\ContactFormHandler
arguments: [#request, #templating, #email_manager]
scope: request
4 Which gives you sthg short and sweet in your controller
/**
* #Route("/", name="contact")
* #Template("AcmeContactBundle:Default:index.html.twig")
*/
public function contactAction(Request $request)
{
/** BTW, you could create a service to create the form too... */
$form = $this->createForm(new ContactType());
$formHandler = $this->container->get('acme_contact.contact_form_handler');
if ($email = $formHandler->process($form)) {
$this->setFlash('success', 'Your email has been sent! Thanks!');
return $this->redirect($this->generateUrl('contact'));
}
}