/   /   /  Magento 2 for developers : add product image to order items in admin order view

Note:

For more extensions and themes visit our store

Magento 2 for developers : add product image to order items in admin order view


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 :

  1.  
  2.                     <block class="Magento\Sales\Block\Adminhtml\Order\View\Items" name="order_items" template="Magento_Sales::order/view/items.phtml">
  3.                         <arguments>
  4.                             <argument name="columns" xsi:type="array">
  5.                                 <item name="product" xsi:type="string" translate="true">Product</item>
  6.                                 <item name="status" xsi:type="string" translate="true">Item Status</item>
  7.                                 <item name="price-original" xsi:type="string" translate="true">Original Price</item>
  8.                                 <item name="price" xsi:type="string" translate="true">Price</item>
  9.                                 <item name="ordered-qty" xsi:type="string" translate="true">Qty</item>
  10.                                 <item name="subtotal" xsi:type="string" translate="true">Subtotal</item>
  11.                                 <item name="tax-amount" xsi:type="string" translate="true">Tax Amount</item>
  12.                                 <item name="tax-percent" xsi:type="string" translate="true">Tax Percent</item>
  13.                                 <item name="discont" xsi:type="string" translate="true">Discount Amount</item>
  14.                                 <item name="total" xsi:type="string" translate="true">Row Total</item>
  15.                             </argument>
  16.                         </arguments>
  17.                         <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">
  18.                             <arguments>
  19.                                 <argument name="columns" xsi:type="array">
  20.                                     <item name="product" xsi:type="string" translate="false">col-product</item>
  21.                                     <item name="status" xsi:type="string" translate="false">col-status</item>
  22.                                     <item name="price-original" xsi:type="string" translate="false">col-price-original</item>
  23.                                     <item name="price" xsi:type="string" translate="false">col-price</item>
  24.                                     <item name="qty" xsi:type="string" translate="false">col-ordered-qty</item>
  25.                                     <item name="subtotal" xsi:type="string" translate="false">col-subtotal</item>
  26.                                     <item name="tax-amount" xsi:type="string" translate="false">col-tax-amount</item>
  27.                                     <item name="tax-percent" xsi:type="string" translate="false">col-tax-percent</item>
  28.                                     <item name="discont" xsi:type="string" translate="false">col-discont</item>
  29.                                     <item name="total" xsi:type="string" translate="false">col-total</item>
  30.                                 </argument>
  31.                             </arguments>
  32.                         </block>
  33.                     </block>
  34.  

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 :

  1.  
  2.     public function getColumns()
  3.     {
  4.         $columns = array_key_exists('columns', $this->_data) ? $this->_data['columns'] : [];
  5.         return $columns;
  6.     }
  7.  

which read the columns from layout and adding it’s .
but the class  DefaultRenderer has other function responsible to use the customs HTML renderer;
  1.  
  2.     public function getColumnHtml(\Magento\Framework\DataObject $item, $column, $field = null)
  3.     {
  4.         $html = '';
  5.         switch ($column) {
  6.             case 'product':
  7.                 if ($this->canDisplayContainer()) {
  8.                     $html .= '<div id="' . $this->getHtmlId() . '">';
  9.                 }
  10.                 $html .= $this->getColumnHtml($item, 'name');
  11.                 if ($this->canDisplayContainer()) {
  12.                     $html .= '</div>';
  13.                 }
  14.                 break;
  15.             case 'status':
  16.                 $html = $item->getStatus();
  17.                 break;
  18.             case 'price-original':
  19.                 $html = $this->displayPriceAttribute('original_price');
  20.                 break;
  21.             case 'tax-amount':
  22.                 $html = $this->displayPriceAttribute('tax_amount');
  23.                 break;
  24.             case 'tax-percent':
  25.                 $html = $this->displayTaxPercent($item);
  26.                 break;
  27.             case 'discont':
  28.                 $html = $this->displayPriceAttribute('discount_amount');
  29.                 break;
  30.             default:
  31.                 $html = parent::getColumnHtml($item, $column, $field);
  32.         }
  33.         return $html;
  34.     }
  35.  

let’s start injecting our Plugins (Interceptors) inside di.xml in the path app/code/Ibnab/OrderItemsImage/etc/adminhtml/di.xml  push :
  1.  
  2. <?xml version="1.0"?>
  3. <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
  4.     <type name="Magento\Sales\Block\Adminhtml\Order\View\Items">
  5.         <plugin name="imageitem_order_prepare_data_source_after" type="Ibnab\OrderItemsImage\Plugin\Adminhtml\AddImage"/>
  6.     </type>
  7.     <type name="Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer">
  8.         <plugin name="imagerenderer_order_prepare_data_source_before_around" type="Ibnab\OrderItemsImage\Plugin\Adminhtml\AddRenderer"/>
  9.     </type>
  10. </config>
  11.  

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: 

  1.  
  2. <?php
  3. namespace Ibnab\OrderItemsImage\Plugin\Adminhtml;
  4. class AddImage {
  5.   /**
  6.      * @param ContextInterface $context
  7.      * @param Url $urlBuilder
  8.      */
  9.     public function __construct(
  10.     ) {
  11.     }
  12.     public function afterGetColumns($items, $result) {
  13.       if (is_array($result)) {
  14.             $newResult['image'] = 'Image';
  15.             foreach($result as $key=>$value){
  16.               $newResult[$key]  = $value;
  17.             }
  18.             $result = $newResult;
  19.         }
  20.      return $result;
  21.     }
  22. }
  23.  

is simple before after method which get the result and add our image as first row of array :
  1.  
  2.             $newResult['image'] = 'Image';
  3.             foreach($result as $key=>$value){
  4.               $newResult[$key]  = $value;
  5.             }
  6.             $result = $newResult;
  7.  

Now inside  Ibnab\OrderItemsImage\Plugin\Adminhtml\AddRenderer (Plugin responsible of customs renderer) add :
  1.  
  2. <?php
  3. namespace Ibnab\OrderItemsImage\Plugin\Adminhtml;
  4. use Magento\Framework\View\Element\UiComponent\ContextInterface;
  5. class AddRenderer {
  6.     /*
  7.      * @var UrlInterface
  8.      */
  9.     protected $_imageHelper;
  10.     protected $_coreRegistry = null;
  11.     /**
  12.      * @param ContextInterface $context
  13.      * @param Url $urlBuilder
  14.      */
  15.     public function __construct(
  16.      \Magento\Framework\Registry $registry, \Ibnab\OrderItemsImage\Helper\Image $imageHelper
  17.     ) {
  18.         $this->_coreRegistry = $registry;
  19.         $this->_imageHelper = $imageHelper;
  20.     }
  21.     public function afterGetColumns($defaultRenderer, $result) {
  22.         if (is_array($result)) {
  23.             $newResult['image'] = 'col-image';
  24.             foreach ($result as $key => $value) {
  25.                 $newResult[$key] = $value;
  26.             }
  27.             $result = $newResult;
  28.         }
  29.         return $result;
  30.     }
  31.     public function beforeGetColumnHtml($defaultRenderer, \Magento\Framework\DataObject $item, $column, $field = null) {
  32.         $html = '';
  33.         switch ($column) {
  34.             case 'image':
  35.                 $this->_coreRegistry->register('is_image_renderer', 1);
  36.                 $this->_coreRegistry->register('ibnab_current_order_item', $item);
  37.                 break;
  38.         }
  39.         return [$item, $column, $field];
  40.     }
  41.     public function afterGetColumnHtml($defaultRenderer, $result) {
  42.         $is_image = $this->_coreRegistry->registry('is_image_renderer');
  43.         $currentItem = $this->_coreRegistry->registry('ibnab_current_order_item');
  44.         $this->_coreRegistry->unregister('is_image_renderer');
  45.         $this->_coreRegistry->unregister('ibnab_current_order_item');
  46.         if ($is_image == 1) {
  47.             return  $this->renderImage($currentItem->getProduct());
  48.         }
  49.         return $result;
  50.     }
  51.     protected function renderImage($product) {
  52.         $this->_imageHelper->addGallery($product);
  53.         $images = $this->_imageHelper->getGalleryImages($product);
  54.         foreach ($images as $image) {
  55.             $item = $image->getData();
  56.             if (isset($item['media_type']) && $item['media_type'] == 'image') {
  57.             $imgPath = isset($item['small_image_url']) ? $item['small_image_url'] : null;    
  58.             return "<img src=".$imgPath." alt=".$product->getName().">";
  59.             }
  60.         }
  61.         return null;
  62.    }
  63. }
  64.  

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   :
  1.  
  2.     public function afterGetColumns($defaultRenderer, $result) {
  3.         if (is_array($result)) {
  4.             $newResult['image'] = 'col-image';
  5.             foreach ($result as $key => $value) {
  6.                 $newResult[$key] = $value;
  7.             }
  8.             $result = $newResult;
  9.         }
  10.         return $result;
  11.  }
  12.  

Easy we have added our custom renderer to our column image $newResult['image'] = 'col-image'
and inside function :
  1.  
  2.     public function beforeGetColumnHtml($defaultRenderer, \Magento\Framework\DataObject $item, $column, $field = null) {
  3.         $html = '';
  4.         switch ($column) {
  5.             case 'image':
  6.                 $this->_coreRegistry->register('is_image_renderer', 1);
  7.                 $this->_coreRegistry->register('ibnab_current_order_item', $item);
  8.                 break;
  9.         }
  10.         return [$item, $column, $field];
  11.     }
  12.  

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:
  1.  
  2.     public function afterGetColumnHtml($defaultRenderer, $result) {
  3.         $is_image = $this->_coreRegistry->registry('is_image_renderer');
  4.         $currentItem = $this->_coreRegistry->registry('ibnab_current_order_item');
  5.         $this->_coreRegistry->unregister('is_image_renderer');
  6.         $this->_coreRegistry->unregister('ibnab_current_order_item');
  7.         if ($is_image == 1) {
  8.             return  $this->renderImage($currentItem->getProduct());
  9.         }
  10.         return $result;
  11.     }
  12.  

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 : 

  1.  
  2.     protected function renderImage($product) {
  3.         $this->_imageHelper->addGallery($product);
  4.         $images = $this->_imageHelper->getGalleryImages($product);
  5.         foreach ($images as $image) {
  6.             $item = $image->getData();
  7.             if (isset($item['media_type']) && $item['media_type'] == 'image') {
  8.             $imgPath = isset($item['small_image_url']) ? $item['small_image_url'] : null;    
  9.             return "<img src=".$imgPath." alt=".$product->getName().">";
  10.             }
  11.         }
  12.         return null;
  13.     }
  14.  

Ok $this->_imageHelper  is an instance of \Ibnab\OrderItemsImage\Helper\Image we have add to get images of product :
  1.  
  2. <?php
  3. namespace Ibnab\OrderItemsImage\Helper;
  4. use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler;
  5. class Image extends \Magento\Framework\App\Helper\AbstractHelper {
  6.   protected $galleryReadHandler;
  7.     /**
  8.      * Catalog Image Helper
  9.      *
  10.      * @var \Magento\Catalog\Helper\Image
  11.      */
  12.     protected $imageHelper;
  13.     public function __construct(
  14.     GalleryReadHandler $galleryReadHandler,  \Magento\Framework\App\Helper\Context $context,\Magento\Catalog\Helper\Image $imageHelper)
  15.     {
  16.         $this->imageHelper = $imageHelper;
  17.         $this->galleryReadHandler = $galleryReadHandler;
  18.         parent::__construct($context);
  19.     }
  20.    /** Add image gallery to $product */
  21.     public function addGallery($product) {
  22.         $this->galleryReadHandler->execute($product);
  23.     }
  24.     public function getGalleryImages(\Magento\Catalog\Api\Data\ProductInterface $product)
  25.     {
  26.         $images = $product->getMediaGalleryImages();
  27.         if ($images instanceof \Magento\Framework\Data\Collection) {
  28.             foreach ($images as $image) {
  29.                 /** @var $image \Magento\Catalog\Model\Product\Image */
  30.                 $image->setData(
  31.                     'small_image_url',
  32.                     $this->imageHelper->init($product, 'product_page_image_small')
  33.                         ->setImageFile($image->getFile())
  34.                         ->getUrl()
  35.                );
  36.             }
  37.         }
  38.         return $images;
  39.     }
  40. }
  41.  

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 :

  1.  
  2.         $this->_imageHelper->addGallery($product);
  3.         $images = $this->_imageHelper->getGalleryImages($product);
  4.         foreach ($images as $image) {
  5.             $item = $image->getData();
  6.             if (isset($item['media_type']) && $item['media_type'] == 'image') {
  7.             $imgPath = isset($item['small_image_url']) ? $item['small_image_url'] : null;    
  8.             return "<img src=".$imgPath." alt=".$product->getName().">";
  9.             }
  10.         }
  11.  

with for foreach statement and  if (isset($item['media_type']) && $item['media_type'] == 'image') { we’re returning the first small image of gallery .

 

Comments

IBNAB is a company made of a group of professionals whose work is providing secure open source solutions. Our company strives for reaching magnificent results with each experience and provides professional open source solutions that cover every part of the business process.