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;

    /**
     * @var \Magento\Widget\Helper\Conditions
     */
    private $conditionsHelper;

    /**
     * @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)
                            }
                        } else {
                            $request['values'] = array_map('htmlspecialchars_decode', $request['values']);
                            if (isset($request['values']['conditions_encoded'])) {
                                $request['values']['conditions'] =
                                    $this->getConditionsHelper()->decode($request['values']['conditions_encoded']);
                            }
                        }
                        $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)
            );
        }
    }

    /**
     * @return \Magento\Widget\Helper\Conditions
     * @deprecated
     */
    private function getConditionsHelper()
    {
        if (!$this->conditionsHelper) {
            $this->conditionsHelper = ObjectManager::getInstance()->get(\Magento\Widget\Helper\Conditions::class);
        }

        return $this->conditionsHelper;
    }
}

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 the 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
      9. Configurable delivery date field

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

Now compatible with Magento 2.3 version.

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!

Process indexing from admin in Magento2

In magento2, you unable to process indexing from admin. You can only set the mode. If you need to run indexing from admin, then this module will help you.

This is independent module, so that this module can’t create any effect to core indexer module. You can use without hesitate.

After install this module go to Admin -> System -> Product Index Management
Download module from here

How to add configurable editor in Magento2 admin form

If you miss create admin module series then click here

So I working with following admin form function


SR\Weblog\Block\Adminhtml\Blog\Edit\Tab\Main

So for editor configuration I am going to create a configuration class


SR\Weblog\Block\Adminhtml\Blog\Editor\Editor

namespace SR\Weblog\Block\Adminhtml\Blog\Editor;


class Editor extends \Magento\Framework\Data\Form\Element\Textarea
{
    /**
     * Adminhtml data
     *
     * @var \Magento\Backend\Helper\Data
     */
    protected $_backendData = null;

    /**
     * Module data
     *
     * @var \Magento\Framework\Module\Manager
     */
    protected $_moduleManager = null;

    /**
     * @var \Magento\Cms\Model\Wysiwyg\Config
     */
    protected $_wysiwygConfig;

    /**
     * @var \Magento\Framework\View\LayoutInterface
     */
    protected $_layout;

    /**
     * @param \Magento\Framework\Data\Form\Element\Factory $factoryElement
     * @param \Magento\Framework\Data\Form\Element\CollectionFactory $factoryCollection
     * @param \Magento\Framework\Escaper $escaper
     * @param \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig
     * @param \Magento\Framework\View\LayoutInterface $layout
     * @param \Magento\Framework\Module\Manager $moduleManager
     * @param \Magento\Backend\Helper\Data $backendData
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\Data\Form\Element\Factory $factoryElement,
        \Magento\Framework\Data\Form\Element\CollectionFactory $factoryCollection,
        \Magento\Framework\Escaper $escaper,
        \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
        \Magento\Framework\View\LayoutInterface $layout,
        \Magento\Framework\Module\Manager $moduleManager,
        \Magento\Backend\Helper\Data $backendData,
        array $data = []
    ) {
        $this->_wysiwygConfig = $wysiwygConfig;
        $this->_layout = $layout;
        $this->_moduleManager = $moduleManager;
        $this->_backendData = $backendData;
        parent::__construct($factoryElement, $factoryCollection, $escaper, $data);
    }

    /**
     * Retrieve additional html and put it at the end of element html
     *
     * @return string
     */
    public function getAfterElementHtml()
    {
        $config = $this->_wysiwygConfig->getConfig();
        $config = json_encode($config->getData());

        $html = parent::getAfterElementHtml();
        if ($this->getIsWysiwygEnabled()) {
            $html = <<<HTML

require([
    'jquery',
    'mage/adminhtml/wysiwyg/tiny_mce/setup'
], function(jQuery){

var config = $config,
    editor;

jQuery.extend(config, {
    settings: {
        theme_advanced_buttons1 : 'bold,italic,|,justifyleft,justifycenter,justifyright,|,' +
            'fontselect,fontsizeselect,|,forecolor,backcolor,|,link,unlink,image,|,bullist,numlist,|,code',
        theme_advanced_buttons2: null,
        theme_advanced_buttons3: null,
        theme_advanced_buttons4: null,
        theme_advanced_statusbar_location: null
    },
    files_browser_window_url: false
});

editor = new tinyMceWysiwygSetup(
    '{$this->getHtmlId()}',
    config
);

editor.turnOn();

jQuery('#{$this->getHtmlId()}')
    .addClass('wysiwyg-editor')
    .data(
        'wysiwygEditor',
        editor
    );
});

HTML;
        }
        return $html;
    }

    /**
     * Check whether wysiwyg enabled or not
     *
     * @return bool
     * @SuppressWarnings(PHPMD.BooleanGetMethodName)
     */
    public function getIsWysiwygEnabled()
    {
        return true;
    }

Now inside


SR\Weblog\Block\Adminhtml\Blog\Edit\Tab\Main

add following line:


$fieldset->addField(
            'content',
            'SR\Weblog\Block\Adminhtml\Blog\Editor\Editor',
            [
                'name' => 'content',
                'required' => true,
            ]
        );

Click here for updated code

How to programmatically create invoice in magento2

If we need to programmatically create invoice for an order then you can create by following way.


// Load the order

 $order = $this->_objectManager->create('Magento\Sales\Model\Order')
    ->loadByAttribute('increment_id', '000000009');

// OR

$order = $this->_objectManager->create('Magento\Sales\Model\Order')
    ->load('1');


if ($order->canInvoice()) {
    // Create invoice for this order
    $invoice = $this->_objectManager->create('Magento\Sales\Model\Service\InvoiceService')->prepareInvoice($order);

    // Make sure there is a qty on the invoice
    if (!$invoice->getTotalQty()) {
        throw new \Magento\Framework\Exception\LocalizedException(
                    __('You can\'t create an invoice without products.')
                );
    }

    // Register as invoice item
    $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
    $invoice->register();

    // Save the invoice to the order
    $transaction = $this->_objectManager->create('Magento\Framework\DB\Transaction')
        ->addObject($invoice)
        ->addObject($invoice->getOrder());

    $transaction->save();

    // Magento\Sales\Model\Order\Email\Sender\InvoiceSender
    $this->invoiceSender->send($invoice);
    
    $order->addStatusHistoryComment(
        __('Notified customer about invoice #%1.', $invoice->getId())
    )
        ->setIsCustomerNotified(true)
        ->save();
}