Category: Magento2

How to get customer id from block when full page cache enable in magento 2

Today I discuss about getting customer_id from block when full page cache(FPC) is enable. If FPC is enable then you can’t get customer id from block just because magento2 reset all customer session. You can get customer session data if your layout set cacheable=”false” in that case magento2 just ignore cache whole page. How? lets dive.

If you open vendor/magento/module-customer/Model/Layout/DepersonalizePlugin.php


/**
 * After generate Xml
 *
 * @param \Magento\Framework\View\LayoutInterface $subject
 * @param \Magento\Framework\View\LayoutInterface $result
 * @return \Magento\Framework\View\LayoutInterface
 */
public function afterGenerateXml(\Magento\Framework\View\LayoutInterface $subject, $result)
{
    if ($this->depersonalizeChecker->checkIfDepersonalize($subject)) {
        $this->visitor->setSkipRequestLogging(true);
        $this->visitor->unsetData();
        $this->session->clearStorage();
        $this->customerSession->clearStorage();
        $this->session->setData(\Magento\Framework\Data\Form\FormKey::FORM_KEY, $this->formKey);
        $this->customerSession->setCustomerGroupId($this->customerGroupId);
        $this->customerSession->setCustomer($this->customerFactory->create()->setGroupId($this->customerGroupId));
    }
    return $result;
}

Now check the if condition, basically this return true/false from following function
vendor/magento/module-page-cache/Model/DepersonalizeChecker.php


/**
 * Check if depersonalize or not
 *
 * @param \Magento\Framework\View\LayoutInterface $subject
 * @return bool
 * @api
 */
public function checkIfDepersonalize(\Magento\Framework\View\LayoutInterface $subject)
{
    return ($this->moduleManager->isEnabled('Magento_PageCache')
        && $this->cacheConfig->isEnabled()
        && !$this->request->isAjax()
        && ($this->request->isGet() || $this->request->isHead())
        && $subject->isCacheable());
}

Here $subject->isCacheable() return false if your layout has ‘cacheable=”false”‘ attribute, lets check
vendor/magento/framework/View/Layout.php


/**
 * Check is exists non-cacheable layout elements
 *
 * @return bool
 */
public function isCacheable()
{
    $this->build();
    $cacheableXml = !(bool)count($this->getXml()->xpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]'));
    return $this->cacheable && $cacheableXml;
}

So if full page cahce enable and cacheable=”false” is not exist then magento 2 reset customer session by


$this->session->clearStorage();
$this->customerSession->clearStorage();

That’s why if you try to get customer session data from block, it’s return always empty basically only group_id is present in customer session data.

So how to get customer information then.

You can follow Magento2 official tutorial.

Now Today I discuss how you can get customer_id if cache enable. Lets dive.

Suppose vendor name ‘SR‘ and module name ‘CustomerSession

Create a contex class [SR/CustomerSession/Model/Customer/Context.php]


namespace SR\CustomerSession\Model\Customer;

class Context
{
    /**
     * Customer authorization cache context
     */
    const CONTEXT_CUSTOMER_ID = 'logged_in_customer_id';
}

Now pluginize Magento\Framework\App\Action\AbstractAction dispatch method.
So create a di.xml [SR/CustomerSession/etc/frontend/di.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\Action\AbstractAction">
        <plugin name="sr-customer-app-action-dispatchController-context-plugin" type="SR\CustomerSession\Plugin\App\Action\Context" sortOrder="15"/>
    </type>
</config>

Now create SR/CustomerSession/Plugin/App/Action/Context.php


namespace SR\CustomerSession\Plugin\App\Action;

use SR\CustomerSession\Model\Customer\Context as CustomerSessionContext;

class Context
{
    /**
     * @var \Magento\Customer\Model\Session
     */
    protected $customerSession;

    /**
     * @var \Magento\Framework\App\Http\Context
     */
    protected $httpContext;

    /**
     * @param \Magento\Customer\Model\Session $customerSession
     * @param \Magento\Framework\App\Http\Context $httpContext
     */
    public function __construct(
        \Magento\Customer\Model\Session $customerSession,
        \Magento\Framework\App\Http\Context $httpContext
    ) {
        $this->customerSession = $customerSession;
        $this->httpContext = $httpContext;
    }

    /**
     * @param \Magento\Framework\App\ActionInterface $subject
     * @param callable $proceed
     * @param \Magento\Framework\App\RequestInterface $request
     * @return mixed
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function aroundDispatch(
        \Magento\Framework\App\ActionInterface $subject,
        \Closure $proceed,
        \Magento\Framework\App\RequestInterface $request
    ) {
        $customerId = $this->customerSession->getCustomerId();
        if(!$customerId) {
            $customerId = 0;
        }

        $this->httpContext->setValue(
            CustomerSessionContext::CONTEXT_CUSTOMER_ID,
            $customerId,
            false
        );

        return $proceed($request);
    }
}

That’s it. Now you can able to get customer id following way even cache enable:


/**
* @var \Magento\Framework\App\Http\Context
*/
$this->httpContext->getValue(SR\CustomerSession\Model\Customer\Context::CONTEXT_CUSTOMER_ID);

Download full module from here

Generate invoice and shipment automatically when a new order is placed in Magento 2

Many merchants wants to create invoice / shipment and complete the order all at once when place order. So today I will discuss about how achieve their need. Although it’s custom needs/requirement so we need to create a custom module for that.

In that case, you need to do is to observe the checkout_submit_all_after event. So lets start.

Here Vendor is ‘SR’ and Module name ‘AutoInvoiceShipment’.

So create a module you can follow this post

Create events.xml [SR/AutoInvoiceShipment/etc/events.xml]

<?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="checkout_submit_all_after">
        <observer name="sr_auto_invoice_shipment_checkout_submit_all_after" instance="SR\AutoInvoiceShipment\Observer\CheckoutAllSubmitAfterObserver"/>
    </event>
</config>

Create observer class for that [SR/AutoInvoiceShipment/Observer/CheckoutAllSubmitAfterObserver.php]

namespace SR\AutoInvoiceShipment\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class CheckoutAllSubmitAfterObserver implements ObserverInterface
{
    /**
     *
     * @var \SR\AutoInvoiceShipment\Helper\Data
     */
    protected $helper;

    /**
     * @param \SR\AutoInvoiceShipment\Helper\Data $helper
     */
    public function __construct(
        \SR\AutoInvoiceShipment\Helper\Data $helper
    ) {
        $this->helper = $helper;
    }

    /**
     *
     * @param Observer $observer
     * @return $this
     */
    public function execute(Observer $observer)
    {
        if(!$this->helper->isEnabled()) {
            return $this;
        }

        $order = $observer->getEvent()->getOrder();
        if(!$order->getId()) {
            return $this;
        }

        $invoice = $this->helper->createInvoice($order);
        if($invoice) {
            $this->helper->createShipment($order, $invoice);
        }

        return $this;
    }
}

Create a helper[SR/AutoInvoiceShipment/Helper/Data.php]

namespace SR\AutoInvoiceShipment\Helper;

use Magento\Framework\App\Helper\AbstractHelper;

class Data extends AbstractHelper
{
    const MODULE_ENABLED = 'sr_auto_invoice_shipment/settings/enabled';

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     *
     * @var \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory
     */
    protected $invoiceCollectionFactory;

    /**
     *
     * @var \Magento\Sales\Model\Service\InvoiceService
     */
    protected $invoiceService;

    /**
     *
     * @var \Magento\Sales\Model\Order\ShipmentFactory
     */
    protected $shipmentFactory;

    /**
     *
     * @var \Magento\Framework\DB\TransactionFactory
     */
    protected $transactionFactory;

    /**
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory $invoiceCollectionFactory
     * @param \Magento\Sales\Model\Service\InvoiceService $invoiceService
     * @param \Magento\Sales\Model\Order\ShipmentFactory $shipmentFactory
     * @param \Magento\Framework\DB\TransactionFactory $transactionFactory
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory $invoiceCollectionFactory,
        \Magento\Sales\Model\Service\InvoiceService $invoiceService,
        \Magento\Sales\Model\Order\ShipmentFactory $shipmentFactory,
        \Magento\Framework\DB\TransactionFactory $transactionFactory
    ) {
        parent::__construct($context);
        $this->scopeConfig = $scopeConfig;
        $this->invoiceCollectionFactory = $invoiceCollectionFactory;
        $this->invoiceService = $invoiceService;
        $this->shipmentFactory = $shipmentFactory;
        $this->transactionFactory = $transactionFactory;
    }

    public function createInvoice($order)
    {
        try {
            $invoices = $this->invoiceCollectionFactory->create()
                ->addAttributeToFilter('order_id', array('eq' => $order->getId()));

            $invoices->getSelect()->limit(1);

            if ((int)$invoices->count() !== 0) {
                return null;
            }

            if(!$order->canInvoice()) {
                return null;
            }

            $invoice = $this->invoiceService->prepareInvoice($order);
            $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
            $invoice->register();
            $invoice->getOrder()->setCustomerNoteNotify(false);
            $invoice->getOrder()->setIsInProcess(true);
            $order->addStatusHistoryComment('Automatically INVOICED', false);
            $transactionSave = $this->transactionFactory->create()->addObject($invoice)->addObject($invoice->getOrder());
            $transactionSave->save();
        } catch (\Exception $e) {
            $order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
            $order->save();
            return null;
        }

        return $invoice;
    }

    public function createShipment($order, $invoice)
    {
        try {
            $shipment = $this->prepareShipment($invoice);
            if ($shipment) {
                $order->setIsInProcess(true);
                $order->addStatusHistoryComment('Automatically SHIPPED', false);
                $this->transactionFactory->create()->addObject($shipment)->addObject($shipment->getOrder())->save();
            }
        } catch (\Exception $e) {
            $order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
            $order->save();
        }
    }

    public function prepareShipment($invoice)
    {
        $shipment = $this->shipmentFactory->create(
            $invoice->getOrder(),
            []
        );

        return $shipment->getTotalQty() ? $shipment->register() : false;
    }

    /**
     * Is the module enabled in configuration.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return $this->scopeConfig->getValue(self::MODULE_ENABLED);
    }
}

That’s it.

If you want an option that you can handle this module on/off. So create a configuration
[SR/AutoInvoiceShipment/etc/adminhtml/system.xml]

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="sr_auto_invoice_shipment" translate="label" type="text" sortOrder="1300" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Auto Invoice Shipment</label>
            <tab>sales</tab>
            <resource>SR_AutoInvoiceShipment::sr_auto_invoice_shipment</resource>
            <group id="settings" translate="label" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Extension Settings</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

Download Full Module From Here

How to integrate recaptcha in magento 2 contact page

Today I discuss, How to integrate google reCaptcha into contact page. So you need to view captcha and validation captcha.

Step 1: Create Module.xml [SR/ReCaptcha/etc/module.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="SR_ReCaptcha" setup_version="2.1.0">
    </module>
</config>

Step 2: Create registration.php [SR/ReCaptcha/registration.php]


\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'SR_ReCaptcha',
    __DIR__
);

Step 3: Create system configuration [SR/ReCaptcha/etc/adminhtml/system.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="sr_reCaptcha">
            Re Captcha
        </tab>
        <section id="reCaptcha" type="text" showInDefault="1">
            <label>Re Captcha Configuration</label>
            <tab>sr_reCaptcha</tab>
            <resource>SR_ReCaptcha::reCaptcha</resource>
            <group id="settings" type="text"  sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                <label>Extension Settings</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="site_key" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Site Key</label>
                </field>
                <field id="secret_key" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Secret Key</label>
                </field>
            </group>
        </section>
    </system>
</config>


Step 4: Assign default value for system configuration [SR/ReCaptcha/etc/config.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <reCaptcha>
            <settings>
                <enabled>0</enabled>
                <site_key></site_key>
                <secret_key></secret_key>
            </settings>
        </reCaptcha>
    </default>
</config>


Step 5: Create helper [SR/ReCaptcha/Helper/Data.php]


namespace SR\ReCaptcha\Helper;

use Magento\Store\Model\ScopeInterface;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    const MODULE_ENABLED = 'reCaptcha/settings/enabled';
    const SITE_KEY = 'reCaptcha/settings/site_key';
    const SECRET_KEY = 'reCaptcha/settings/secret_key';

    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * Data constructor.
     *
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
    ) {
        parent::__construct($context);
        $this->storeManager = $storeManager;
        $this->scopeConfig = $scopeConfig;
    }

    /**
     * Is the module enabled in configuration.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return $this->getStoreConfig(self::MODULE_ENABLED);
    }

    /**
     * The recaptcha site key.
     *
     *
     * @return string
     */
    public function getSiteKey()
    {
        return $this->getStoreConfig(self::SITE_KEY);
    }

    /**
     * The recaptcha secret key.
     *
     *
     * @return string
     */
    public function getSecretKey()
    {
        return $this->getStoreConfig(self::SECRET_KEY);
    }

    public function getStoreConfig($path)
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE, $store);
    }

    public function getStoreId()
    {
        return $this->storeManager->getStore()->getStoreId();
    }
}

Step 6: Layout configuration [SR/ReCaptcha/view/frontend/layout/contact_index_index.xml]


<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="form.additional.info">
            <block class="SR\ReCaptcha\Block\ReCaptcha" name="recaptchaForm" template="SR_ReCaptcha::captcha.phtml" after="-" cacheable="false" />
        </referenceContainer>
    </body>
</page>


Step 7: Create phtml file [SR/ReCaptcha/view/frontend/templates/captcha.phtml]


<?php /** @var $block \SR\ReCaptcha\Block\ReCaptcha */ ?>
<?php if ($block->isEnabled()): ?>
    https://www.google.com/recaptcha/api.js
    
getSiteKey();?>==">
<?php endif; ?>

Step 7: Create a plugin [SR/ReCaptcha/etc/frontend/di.xml]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Contact\Controller\Index\Post">
        <plugin name="reCaptcha_Post_Action" type="SR\ReCaptcha\Plugin\Contact\Controller\Index\Post" sortOrder="1"/>
    </type>
</config>


Step 8: Create a plugin class [SR/ReCaptcha/Plugin/Contact/Controller/Index/Post.php]


namespace SR\ReCaptcha\Plugin\Contact\Controller\Index;

class Post
{
    /**
     * @var \Magento\Framework\Controller\Result\RedirectFactory
     */
    protected $resultRedirectFactory;

    /**
     * @var \Magento\Framework\Message\ManagerInterface
     */
    protected $messageManager;

    /**
     * @var \SR\ReCaptcha\Helper\Data $dataHelper
     */
    protected $dataHelper;

    /**
     * @param \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory
     * @param \Magento\Framework\Message\ManagerInterface $messageManager
     * @param \SR\ReCaptcha\Helper\Data $dataHelper
     */
    public function __construct(
        \Magento\Framework\Controller\Result\RedirectFactory $resultRedirectFactory,
        \Magento\Framework\Message\ManagerInterface $messageManager,
        \SR\ReCaptcha\Helper\Data $dataHelper
    ) {
        $this->resultRedirectFactory = $resultRedirectFactory;
        $this->messageManager = $messageManager;
        $this->dataHelper = $dataHelper;
    }

    public function aroundExecute(
        \Magento\Contact\Controller\Index\Post $subject,
        \Closure $proceed
    ) {
        if($this->dataHelper->isEnabled()) {
            $recaptcha_response_field = $subject->getRequest()->getPost('g-recaptcha-response');
            if($recaptcha_response_field) {
                $secretKey = $this->dataHelper->getSecretKey();
                $response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".$secretKey."&response=".$recaptcha_response_field."&remoteip=".$_SERVER['REMOTE_ADDR']);
                $result = json_decode($response, true);
                if(isset($result['success']) && ($result['success'])) {
                    return $proceed();
                } else {
                    $resultRedirect = $this->resultRedirectFactory->create();
                    $this->messageManager->addError(
                        __('There was an error with the recaptcha code, please try again.')
                    );
                    $resultRedirect->setPath('contact/index');
                    return $resultRedirect;
                }
            } else {
                $this->messageManager->addError(
                    __('There was an error with the recaptcha code, please try again.')
                );
                $resultRedirect = $this->resultRedirectFactory->create();
                $resultRedirect->setPath('contact/index/index');
                return $resultRedirect;
            }
        }

        return $proceed();
    }
}

Download full module from here

After installation for configuration go to Store -> configuration
re-captcha config

How to use html content in magento2 widget

Today I am going to discuss about some limitation of Magento2 widget. You can create different type of widget with different input field. Suppose you create a TextArea and you try to entry some html content here, then what happened? Ideally you can’t do in default Magento2 installation, it just breaks the edit functionality. So in that case you need some care about this. So today I build an extension for overcome this situation.

So need to overwrite widget save and load controller.

Step 1: Pluginize ‘BuildWidget’ controller and ‘LoadOptions’ controller


# SR/RewriteWidget/etc/adminhtml/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Widget\Controller\Adminhtml\Widget\BuildWidget">
        <plugin name="rewrite_widget_build_widget" type="SR\RewriteWidget\Plugin\Widget\Controller\Adminhtml\Widget\BuildWidget" sortOrder="1"/>
    </type>
    <type name="Magento\Widget\Controller\Adminhtml\Widget\LoadOptions">
        <plugin name="rewrite_widget_load_options" type="SR\RewriteWidget\Plugin\Widget\Controller\Adminhtml\Widget\LoadOptions" sortOrder="1"/>
    </type>
</config>

Step 2: Create SR/RewriteWidget/Plugin/Widget/Controller/Adminhtml/Widget/BuildWidget.php


namespace SR\RewriteWidget\Plugin\Widget\Controller\Adminhtml\Widget;

class BuildWidget
{
    /**
     * @var \Magento\Widget\Model\Widget
     */
    protected $widget;

    /**
     * @var \SR\RewriteWidget\Helper\Data
     */
    protected $helper;

    /**
     * @param \Magento\Widget\Model\Widget $widget
     * @param \SR\RewriteWidget\Helper\Data $helper
     */
    public function __construct(
        \Magento\Widget\Model\Widget $widget,
        \SR\RewriteWidget\Helper\Data $helper
    ) {
        $this->widget = $widget;
        $this->helper = $helper;
    }

    /**
     * Format widget pseudo-code for inserting into wysiwyg editor
     *
     * @return void
     */
    public function aroundExecute(
        \Magento\Widget\Controller\Adminhtml\Widget\BuildWidget $subject,
        \Closure $proceed
    ) {
        $type = $subject->getRequest()->getPost('widget_type');
        $params = $subject->getRequest()->getPost('parameters', []);
        $asIs = $subject->getRequest()->getPost('as_is');

        if($type == 'SR\RewriteWidget\Block\Widget\Content') {

            foreach($params as $key => $value) {
                $params[$key] = $this->helper->encodeWidgetValues($value);
            }
        }

        $html = $this->widget->getWidgetDeclaration($type, $params, $asIs);
        $subject->getResponse()->setBody($html);
    }
}

Step 3: Create SR/RewriteWidget/Plugin/Widget/Controller/Adminhtml/Widget/LoadOptions.php


namespace SR\RewriteWidget\Plugin\Widget\Controller\Adminhtml\Widget;

class LoadOptions
{
    /**
     * @var \Magento\Framework\ObjectManagerInterface
     */
    protected $objectManager;

    /**
     * @var \Magento\Framework\App\ViewInterface
     */
    protected $view;


    /**
     * @param \Magento\Framework\ObjectManagerInterface $objectManager
     * @param \Magento\Framework\App\ViewInterface $view
     */
    public function __construct(
        \Magento\Framework\ObjectManagerInterface $objectManager,
        \Magento\Framework\App\ViewInterface $view
    ) {
        $this->view = $view;
        $this->objectManager = $objectManager;
    }
    /**
     * Ajax responder for loading plugin options form
     *
     * @return void
     */
    public function aroundExecute(
        \Magento\Widget\Controller\Adminhtml\Widget\LoadOptions $subject,
        \Closure $proceed
    ) {
        try {
            $this->view->loadLayout();
            if ($paramsJson = $subject->getRequest()->getParam('widget')) {
                $request = $this->objectManager->get('Magento\Framework\Json\Helper\Data')->jsonDecode($paramsJson);
                if (is_array($request)) {
                    $optionsBlock = $this->view->getLayout()->getBlock('wysiwyg_widget.options');
                    if (isset($request['widget_type'])) {
                        $optionsBlock->setWidgetType($request['widget_type']);
                    }
                    if (isset($request['values'])) {
                        if($optionsBlock->getWidgetType() == 'SR\RewriteWidget\Block\Widget\Content') {
                            $helper = $this->objectManager->create('SR\RewriteWidget\Helper\Data');
                            foreach($request['values'] as $key => $value) {
                                $request['values'][$key] = $helper->decodeWidgetValues($value)
                            }
                        }
                        $optionsBlock->setWidgetValues($request['values']);
                    }
                }
                $this->view->renderLayout();
            }
        } catch (\Magento\Framework\Exception\LocalizedException $e) {
            $result = ['error' => true, 'message' => $e->getMessage()];
            $subject->getResponse()->representJson(
                $this->objectManager->get('Magento\Framework\Json\Helper\Data')->jsonEncode($result)
            );
        }
    }
}

Step 3: Create SR/RewriteWidget/Helper/Data.php


namespace SR\RewriteWidget\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    protected $_reservedData = array(
        'type', 'name_in_layout', 'area', 'module_name',
        'name', '_is_changed', '_renderer_name'
    );

    public function decodeWidgetValues($values)
    {
        if (!is_array($values)) {
            return base64_decode(strtr($values, ':_-', '+/='));
        }

        foreach ($values as $key => $value) {
            if ( is_scalar($value) && !in_array($key, $this->_reservedData) ) {
                $values[$key] = base64_decode(strtr($values, ':_-', '+/='));
            }
        }

        return $values;
    }

    public function encodeWidgetValues($values)
    {
        if ( !is_array($values) && !in_array($values, $this->_reservedData) ) {
            return strtr(base64_encode($values), '+/=', ':_-');
        }

        foreach ($values as $key => $value) {
            if (is_scalar($value)) {
                $values[$key] = strtr(base64_encode($value), '+/=', ':_-');
            }
        }

        return $values;
    }
}

And finally, in my widget block, I had to decode all data on-the-fly:


# SR\RewriteWidget\Block\Widget\Content

namespace SR\RewriteWidget\Block\Widget;

class Content extends \Magento\Framework\View\Element\Template implements \Magento\Widget\Block\BlockInterface
{
	/**
	 * @var \SR\RewriteWidget\Helper\Data
	 */
	protected $helper;

	/**
	 * @var \Magento\Cms\Model\Template\FilterProvider
	 */
	protected $filterProvider;

	/**
	 * Constructor
	 *
	 * @param \Magento\Framework\View\Element\Template\Context $context
	 * @param \SR\RewriteWidget\Helper\Data $helper
	 * @param array $data
	 */
	public function __construct(
		\Magento\Framework\View\Element\Template\Context $context,
		\SR\RewriteWidget\Helper\Data $helper,
		\Magento\Cms\Model\Template\FilterProvider $filterProvider,
		array $data = []
	) {
		parent::__construct($context, $data);
		$this->helper = $helper;
		$this->filterProvider = $filterProvider;
	}

	public function getData($key = '', $index = null)
	{
		if ('' === $key) {
			$data = $this->helper->decodeWidgetValues($this->_data);
		} else {
			$data = parent::getData($key, $index);
			if (is_scalar($data)) {
				$data = $this->helper->decodeWidgetValues($data);
			}

			$data = $this->filterProvider->getPageFilter()->filter($data);
		}

		return $data;
	}
}

How to Create a Custom Logger in Magento 2?

Magento 2 logger component is based on Monolog. Magento 2 has three types of logger handler class.

  1. System
  2. Debug
  3. Exception

Today I create my own logger, How to?

Step 1: Create module.xml


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="SR_CustomLogger" setup_version="2.0.0">
    </module>
</config>

Step 2: Create registration.php


\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'SR_CustomLogger',
    __DIR__
);

Step 3: create di.xml


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="SR\CustomLogger\Logger\Handler">
        <arguments>
            <argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
        </arguments>
    </type>
    <type name="SR\CustomLogger\Logger\Logger">
        <arguments>
            <argument name="name" xsi:type="string">customLogger
            <argument name="handlers"  xsi:type="array">
                <item name="system" xsi:type="object">SR\CustomLogger\Logger\Handler</item>
            </argument>
        </arguments>
    </type>
</config>


Step 4: Create Handler.php


namespace SR\CustomLogger\Logger;

class Handler extends \Magento\Framework\Logger\Handler\Base
{
    protected $fileName = '/var/log/custom_logger.log';
    protected $loggerType = \Monolog\Logger::INFO;
}

Step 5: Create Logger.php


namespace SR\CustomLogger\Logger;


class Logger extends \Monolog\Logger
{

}

Done!

So now test new logger. Here I am going to inject login event observer.

So create a events.xml [SR/CustomLogger/etc/frontend/events.xml]


<?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="customer_customer_authenticated">
        <observer name="custom_logger_customer_authenticated" instance="SR\CustomLogger\Observer\Authenticated" />
    </event>
</config>

Create a Authenticated.php observer for that.



namespace SR\CustomLogger\Observer;

use Magento\Framework\Event\ObserverInterface;

class Authenticated implements ObserverInterface
{
    /**
     * @var \SR\CustomLogger\Logger\Logger $logger
     */
    protected $logger;

    /**
     * @param \SR\CustomLogger\Logger\Logger $logger
     */
    public function __construct(
        \SR\CustomLogger\Logger\Logger $logger
    ) {
        $this->logger = $logger;
    }

    /**
     * Upgrade customer password hash when customer has logged in
     *
     * @param \Magento\Framework\Event\Observer $observer
     * @return void
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        /** @var \Magento\Customer\Model\Customer $model */
        $model = $observer->getEvent()->getData('model');
        if($model->getId()) {
            $this->logger->info('TEST Custom Logger');
        }
    }
}

Download full module from Here

Magento 2 Order Delivery Date extension

The Order Delivery Date Extension gives your customers possibility to choose the date on which they want the products purchased from your store to be delivered.

Feature:

      1. Simple and quick installation
      2. Allows users to provide expected shipping arrival date
      3. Users can also write comments while placing an order from your online shop
      4. Option to display shipping arrival date and comment in order view page
      5. Previous date selection is not possible
      6. Disable week off days (like Saturday and Sunday) [Stores -> Configuration -> Sales -> Order Delivery Date Settings]
      7. 100% open source
      8. No limitations, no extra costs

Download ‘Order Delivery Date’ extension for Magento 2 from here

Enjoy!

How to create custom product type in magento2

Sometimes you need some extra product type for some needs. So today I discuss about how you can achieve this.

1. Create module.xml [app/code/SR/CustomProductType/etc]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="SR_CustomProductType" setup_version="2.0.0">
    </module>
</config>

2. Create registration.php [app/code/SR/CustomProductType]


<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'SR_CustomProductType',
    __DIR__
);


3. Create composer.json [app/code/SR/CustomProductType]


{
    "name": "sr/module-custom-product-type",
    "description": "N/A",
    "require": {
        "php": "~5.5.0|~5.6.0|~7.0.0",
        "magento/module-store": "100.0.0",
        "magento/module-backend": "100.0.0",
        "magento/framework": "100.0.0",
        "lib-libxml": "*"
    },
    "type": "magento2-module",
    "version": "100.0.0",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [ "registration.php" ],
        "psr-4": {
            "SR\\CustomProductType\\": ""
        }
    }
}

In Magento2, xml configuration file are divided into small file. So for product type, you need to create product_types.xml inside [app/code/SR/CustomProductType/etc]


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd">
    <type name="customProductType" label="Custom Product Type" modelInstance="SR\CustomProductType\Model\Product\Type\CustomType" indexPriority="100" sortOrder="100" isQty="true">
        <priceModel instance="SR\CustomProductType\Model\Product\Price" />
        <customAttributes>
            <attribute name="refundable" value="true"/>
            <attribute name="taxable" value="true"/>
        </customAttributes>
    </type>
    <composableTypes>
        <type name="customProductType" />
    </composableTypes>
</config>


Here,
name: custom product type code
label: Type view
modelInstance: Model class
priceModel: Price model, you can calculate price your custom need

So now create modelInstance [SR/CustomProductType/Model/Product/Type/CustomType.php]


<?php
namespace SR\CustomProductType\Model\Product\Type;

class CustomType extends \Magento\Catalog\Model\Product\Type\AbstractType
{
    /**
     * Delete data specific for Custom product type
     *
     * @param \Magento\Catalog\Model\Product $product
     * @return void
     */
    public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product)
    {
    }
}

Create priceModel [SR/CustomProductType/Model/Product/Price.php]


<?php
namespace SR\CustomProductType\Model\Product;

class Price extends \Magento\Catalog\Model\Product\Type\Price
{

}

Now create a setup installer for this module
[SR/CustomProductType/Setup/InstallData.php]


<?php
namespace SR\CustomProductType\Setup;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * Class InstallData
 *
 * @package 
 */
class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory
     *
     * @var EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * Init
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        $attributes = [
            'cost',
            'price',
            'special_price',
            'special_from_date',
            'special_to_date',
            'weight',
            'tax_class_id'
        ];

        foreach ($attributes as $attributeCode) {
            $relatedProductTypes = explode(
                ',',
                $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode, 'apply_to')
            );
            if (!in_array('customProductType', $relatedProductTypes)) {
                $relatedProductTypes[] = 'customProductType';
                $eavSetup->updateAttribute(
                    \Magento\Catalog\Model\Product::ENTITY,
                    $attributeCode,
                    'apply_to',
                    implode(',', $relatedProductTypes)
                );
            }
        }
    }
}


Run following command


php bin/magento setup:upgrade

You can download full module from here.

That’s it!