Today we will add useful course , So you can add a thumbnail image to items grid inside admin order view , the seller need this feature to detect quickly which item has sold exactly .
You can download the Free extension Magento 2 Order Items Image (Clickable) At Admin
Ok the our vendor is Ibnab the extension name is OrderItemsImage inside registration.php and module.xml the full name is Ibnab_OrderItemsImage.
Inside this tutorial we will use the technique of Plugins (Interceptors) to inject our column to grid and render the product image of current item .
More info about Plugins (Interceptors) :
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html?source=pepperjam&publisherId=96525&clickId=2614425708
If you open sales_order_view.xml inside vendor/magento/module-sales/view/adminhtml/layout/sales_order_view.xml
you will find this bit of code :
<block class="Magento\Sales\Block\Adminhtml\Order\View\Items" name="order_items" template="Magento_Sales::order/view/items.phtml"> <arguments> <argument name="columns" xsi:type="array"> <item name="product" xsi:type="string" translate="true">Product</item> <item name="status" xsi:type="string" translate="true">Item Status</item> <item name="price-original" xsi:type="string" translate="true">Original Price</item> <item name="price" xsi:type="string" translate="true">Price</item> <item name="ordered-qty" xsi:type="string" translate="true">Qty</item> <item name="subtotal" xsi:type="string" translate="true">Subtotal</item> <item name="tax-amount" xsi:type="string" translate="true">Tax Amount</item> <item name="tax-percent" xsi:type="string" translate="true">Tax Percent</item> <item name="discont" xsi:type="string" translate="true">Discount Amount</item> <item name="total" xsi:type="string" translate="true">Row Total</item> </argument> </arguments> <block class="Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer" as="default" name="default_order_items_renderer" template="Magento_Sales::order/view/items/renderer/default.phtml"> <arguments> <argument name="columns" xsi:type="array"> <item name="product" xsi:type="string" translate="false">col-product</item> <item name="status" xsi:type="string" translate="false">col-status</item> <item name="price-original" xsi:type="string" translate="false">col-price-original</item> <item name="price" xsi:type="string" translate="false">col-price</item> <item name="qty" xsi:type="string" translate="false">col-ordered-qty</item> <item name="subtotal" xsi:type="string" translate="false">col-subtotal</item> <item name="tax-amount" xsi:type="string" translate="false">col-tax-amount</item> <item name="tax-percent" xsi:type="string" translate="false">col-tax-percent</item> <item name="discont" xsi:type="string" translate="false">col-discont</item> <item name="total" xsi:type="string" translate="false">col-total</item> </argument> </arguments> </block> </block>
the items columns and renderer has injected by arguments , we have 2 class responsible :
Magento\Sales\Block\Adminhtml\Order\View\Items to add columns , and Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer to add custom renderer to every of our columns .
The two class has the function :
public function getColumns() { return $columns; }
which read the columns from layout and adding it’s .
but the class DefaultRenderer has other function responsible to use the customs HTML renderer;
public function getColumnHtml(\Magento\Framework\DataObject $item, $column, $field = null) { $html = ''; switch ($column) { case 'product': if ($this->canDisplayContainer()) { $html .= '<div id="' . $this->getHtmlId() . '">'; } $html .= $this->getColumnHtml($item, 'name'); if ($this->canDisplayContainer()) { $html .= '</div>'; } break; case 'status': $html = $item->getStatus(); break; case 'price-original': $html = $this->displayPriceAttribute('original_price'); break; case 'tax-amount': $html = $this->displayPriceAttribute('tax_amount'); break; case 'tax-percent': $html = $this->displayTaxPercent($item); break; case 'discont': $html = $this->displayPriceAttribute('discount_amount'); break; default: $html = parent::getColumnHtml($item, $column, $field); } return $html; }
let’s start injecting our Plugins (Interceptors) inside di.xml in the path app/code/Ibnab/OrderItemsImage/etc/adminhtml/di.xml push :
<?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\Sales\Block\Adminhtml\Order\View\Items"> <plugin name="imageitem_order_prepare_data_source_after" type="Ibnab\OrderItemsImage\Plugin\Adminhtml\AddImage"/> </type> <type name="Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer"> <plugin name="imagerenderer_order_prepare_data_source_before_around" type="Ibnab\OrderItemsImage\Plugin\Adminhtml\AddRenderer"/> </type> </config>
Our first Plugins (Interceptors) Ibnab\OrderItemsImage\Plugin\Adminhtml\AddImage applied on class Magento\Sales\Block\Adminhtml\Order\View\Items and will observe getColumns() by using after method , ok inside our class Ibnab\OrderItemsImage\Plugin\Adminhtml\AddImage add:
<?php namespace Ibnab\OrderItemsImage\Plugin\Adminhtml; class AddImage { /** * @param ContextInterface $context * @param Url $urlBuilder */ public function __construct( ) { } public function afterGetColumns($items, $result) { $newResult['image'] = 'Image'; foreach($result as $key=>$value){ $newResult[$key] = $value; } $result = $newResult; } return $result; } }
is simple before after method which get the result and add our image as first row of array :
$newResult['image'] = 'Image'; foreach($result as $key=>$value){ $newResult[$key] = $value; } $result = $newResult;
Now inside Ibnab\OrderItemsImage\Plugin\Adminhtml\AddRenderer (Plugin responsible of customs renderer) add :
<?php namespace Ibnab\OrderItemsImage\Plugin\Adminhtml; use Magento\Framework\View\Element\UiComponent\ContextInterface; class AddRenderer { /* * @var UrlInterface */ protected $_imageHelper; protected $_coreRegistry = null; /** * @param ContextInterface $context * @param Url $urlBuilder */ public function __construct( \Magento\Framework\Registry $registry, \Ibnab\OrderItemsImage\Helper\Image $imageHelper ) { $this->_coreRegistry = $registry; $this->_imageHelper = $imageHelper; } public function afterGetColumns($defaultRenderer, $result) { $newResult['image'] = 'col-image'; foreach ($result as $key => $value) { $newResult[$key] = $value; } $result = $newResult; } return $result; } public function beforeGetColumnHtml($defaultRenderer, \Magento\Framework\DataObject $item, $column, $field = null) { $html = ''; switch ($column) { case 'image': $this->_coreRegistry->register('is_image_renderer', 1); $this->_coreRegistry->register('ibnab_current_order_item', $item); break; } return [$item, $column, $field]; } public function afterGetColumnHtml($defaultRenderer, $result) { $is_image = $this->_coreRegistry->registry('is_image_renderer'); $currentItem = $this->_coreRegistry->registry('ibnab_current_order_item'); $this->_coreRegistry->unregister('is_image_renderer'); $this->_coreRegistry->unregister('ibnab_current_order_item'); if ($is_image == 1) { return $this->renderImage($currentItem->getProduct()); } return $result; } protected function renderImage($product) { $this->_imageHelper->addGallery($product); $images = $this->_imageHelper->getGalleryImages($product); foreach ($images as $image) { $item = $image->getData(); return "<img src=".$imgPath." alt=".$product->getName().">"; } } return null; } }
is bit complex we have push two arguments to constructor \Magento\Framework\Registry $registry, \Ibnab\OrderItemsImage\Helper\Image $imageHelper we will see the utility at every function let’s start by :
public function afterGetColumns($defaultRenderer, $result) { $newResult['image'] = 'col-image'; foreach ($result as $key => $value) { $newResult[$key] = $value; } $result = $newResult; } return $result; }
Easy we have added our custom renderer to our column image $newResult['image'] = 'col-image';
and inside function :
public function beforeGetColumnHtml($defaultRenderer, \Magento\Framework\DataObject $item, $column, $field = null) { $html = ''; switch ($column) { case 'image': $this->_coreRegistry->register('is_image_renderer', 1); $this->_coreRegistry->register('ibnab_current_order_item', $item); break; } return [$item, $column, $field]; }
We have just added to register to sign that our columns image and item parent has been passed to function getColumnHtml by the foreach statement to use inside afterGetColumnHtml:
public function afterGetColumnHtml($defaultRenderer, $result) { $is_image = $this->_coreRegistry->registry('is_image_renderer'); $currentItem = $this->_coreRegistry->registry('ibnab_current_order_item'); $this->_coreRegistry->unregister('is_image_renderer'); $this->_coreRegistry->unregister('ibnab_current_order_item'); if ($is_image == 1) { return $this->renderImage($currentItem->getProduct()); } return $result; }
This function has role to detect that the system now trying to render our image column , So we intervening to customize the result by custom renderer return $this->renderImage($currentItem->getProduct()); , the $item is instance of Magento\Sales\Model\Order\Item and has function getProduct :
protected function renderImage($product) { $this->_imageHelper->addGallery($product); $images = $this->_imageHelper->getGalleryImages($product); foreach ($images as $image) { $item = $image->getData(); return "<img src=".$imgPath." alt=".$product->getName().">"; } } return null; }
Ok $this->_imageHelper is an instance of \Ibnab\OrderItemsImage\Helper\Image we have add to get images of product :
<?php namespace Ibnab\OrderItemsImage\Helper; use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; class Image extends \Magento\Framework\App\Helper\AbstractHelper { protected $galleryReadHandler; /** * Catalog Image Helper * * @var \Magento\Catalog\Helper\Image */ protected $imageHelper; public function __construct( GalleryReadHandler $galleryReadHandler, \Magento\Framework\App\Helper\Context $context,\Magento\Catalog\Helper\Image $imageHelper) { $this->imageHelper = $imageHelper; $this->galleryReadHandler = $galleryReadHandler; parent::__construct($context); } /** Add image gallery to $product */ public function addGallery($product) { $this->galleryReadHandler->execute($product); } public function getGalleryImages(\Magento\Catalog\Api\Data\ProductInterface $product) { $images = $product->getMediaGalleryImages(); if ($images instanceof \Magento\Framework\Data\Collection) { foreach ($images as $image) { /** @var $image \Magento\Catalog\Model\Product\Image */ $image->setData( 'small_image_url', $this->imageHelper->init($product, 'product_page_image_small') ->setImageFile($image->getFile()) ->getUrl() ); } } return $images; } }
Is general class which extracting images from product , your can get 'small_image_url' or 'medium_image_url' …
back to our function renderImage($product) in our plugin :
$this->_imageHelper->addGallery($product); $images = $this->_imageHelper->getGalleryImages($product); foreach ($images as $image) { $item = $image->getData(); return "<img src=".$imgPath." alt=".$product->getName().">"; } }
with for foreach statement and if (isset($item['media_type']) && $item['media_type'] == 'image') { we’re returning the first small image of gallery .
Comments