Chapters Close

Let me discuss one interesting problem which I faced with Magento 2.

In Magento 2 Store, I’ve faced an issue with the product URL to access the product page. I need to remove the category path from the Product URL when I access any product page.

I found one admin configuration in Magento Admin at the below path to remove the category path from the product URL.

Path: Admin > STORES > Settings > Configuration > CATALOG > Catalog > Search Engine Optimization

  • Set “Use Categories Path for Product URLs” option to No.
  • After update this configuration, please clean config and full_page cache types and check the product URL.

Now, You can see the category path is removed when you access the product page from the product listing page.

But still, I can access the product page using the product URL with the category path.

For example,
Let’s take an example of the Magento Sample Data’s product name with “Olivia 1/4 Zip Light Jacket”

You can access the product detail page with below URLs by directly entering in the Address Bar of the browser:

Here, I need to prevent the product page access when the URL includes the category path.

So As a solution, I decided If I got the category path in product URL I’ll redirect it to the product URL without category path.

I’ve achieved this solution by creating an after Plugin on findOneByData() function of the Magento\UrlRewrite\Model\Storage\AbstractStorage model class.

Please follow the below steps to implement this solution to omit the category path from the product URL to access the product page.

Step 1: Create a custom module under app/code directory with two require file registration.php and etc/module.xml.

File Path: /app/code/Aureatelabs/DemoModule/registration.php

  • Where Aureatelabs is a vendor name and DemoModule is a module name.
<?php
/**
 * DemoModule
 *
 * Do not edit or add to this file if you wish to upgrade to newer versions in the future.
 * If you wish to customize this module for your needs.
 *
 * @category   DemoModule
 * @package    Aureatelabs_DemoModule
 * @license    http://www.gnu.org/licenses/gpl-3.0.html
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Aureatelabs_DemoModule',
    __DIR__
);
File Path: /app/code/Aureatelabs/DemoModule/etc/module.xml
<?xml version="1.0" ?>
<!--
/**
 * DemoModule
 *
 * Do not edit or add to this file if you wish to upgrade to newer versions in the future.
 * If you wish to customize this module for your needs.
 *
 * @category   DemoModule
 * @package    Aureatelabs_DemoModule
 * @license    http://www.gnu.org/licenses/gpl-3.0.html
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
	<module name="Aureatelabs_DemoModule" setup_version="1.0.0">
	  <sequence>
            <module name="Magento_Catalog"/>
            <module name="Magento_UrlRewrite"/>
        </sequence>
	</module>
</config>

Step 2: Create a di.xml file under etc directory to define a plugin.

File Path: /app/code/Aureatelabs/DemoModule/etc/di.xml
<?xml version="1.0" ?>
<!--
/**
 * DemoModule
 *
 * Do not edit or add to this file if you wish to upgrade to newer versions in the future.
 * If you wish to customize this module for your needs.
 *
 * @category   DemoModule
 * @package    Aureatelabs_DemoModule
 * @license    http://www.gnu.org/licenses/gpl-3.0.html
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\UrlRewrite\Model\Storage\AbstractStorage">
        <plugin disabled="false" name="Plugin_Magento_Checkout_Block_Onepage" sortOrder="10" type="Aureatelabs\DemoModule\Plugin\FindOneByData"/>
    </type>
</config>

Step 3: Create a plugin class under Plugin directory.

File Path: /app/code/Aureatelabs/DemoModule/Plugin/FindOneByData.php
<?php

namespace Aureatelabs\DemoModule\Plugin;

use Magento\Framework\App\Config\ScopeConfigInterface;

/**
 * Class FindOneByData
 * @package Aureatelabs\DemoModule\Plugin
 */
class FindOneByData
{
    /**
     * @var ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * FindOneByData constructor.
     * @param ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig
    ) {
        $this->scopeConfig = $scopeConfig;
    }

    /**
     * @param \Magento\UrlRewrite\Model\Storage\AbstractStorage $subject
     * @param $result
     * @param array $data
     * @return mixed
     */
    public function afterFindOneByData(
        \Magento\UrlRewrite\Model\Storage\AbstractStorage $subject,
        $result,
        array $data
    ) {
        if(!empty($result) && !(bool)$this->scopeConfig->getValue('catalog/seo/product_use_categories')
            && $result->getEntityType() == 'product'
            && (strpos(trim($result->getRequestPath(), '/'), '/') !== false)
        ) {
            $requestPathArr = explode('/', $result->getRequestPath());
            if (count($requestPathArr) > 1) {
                $newRequestPath = end($requestPathArr);
                $result->setTargetPath($newRequestPath);
                $result->setRedirectType(301);
            }
        }
        return $result;
    }
}

This solution will work if the “Use Categories Path for Product URLs” option is set to No from the Magento backend. The Magento configuration path for this option is mentioned above.

Step 4: Please enabled and install the module using the below commands.

php bin/magento module:enable Aureatelabs_DemoModule
php bin/magento setup:upgrade
php bin/magento setup:di:compile

Please verify module functionality by entering product URL with category path directly in the address bar of the browser, it will redirect to the product URL without category path.

Enjoy Coding.

Thank you.

How to add a column to the tier price in Magento 2 admin?

Apr 24th, 2020 7 min to read

Here, In this blog, I’ll guide you to add columns in the tier price grid of the product. Sometimes it’s essential to understand client’s Magento web development requirements by overriding the core modules. Here, I am sharing a use case in which we’ve to override the tier price functionality.

Use Case: Our client has a few products which can be sold separately or in a bulk. Let’s take the example of soap. Users can purchase the soap single or in a pack of 8 to which they called family package and also there is one more option, Users can also purchase a pack of 24 to which they called Box.

To fulfill the client requirement we’ve decided to override the tier price functionality and divided the solution into the two parts. 

  • Add a secondary unit column in tier price
  • Display units on the frontend by using the dropdown.

This blog consists of a first solution which is, to add a column in the tier price grid. To do so, you have to do the below things in your extension.

  1. Add a required field in the tier price table
  2. Add a field in the grid
  3. Override save & update handlers

Let’s follow the above steps and customize the tier price.

1. Add a required field in the tier price table:

Here, we’ll add the field in the tier price table using the database schema. To do so, create the db_schema.xml in the etc folder and add a below code in it.


<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
   <table name="catalog_product_entity_tier_price" resource="default" engine="innodb" comment="Tier Pricing Table">
       <column xsi:type="varchar" name="{KEY}" nullable="true" length="255" comment="comment" />
   </table>
</schema>

2. Add a field in the grid:

We’ll add a field in a tier price grid using the modifiers by the following way.  First we’ll create a di.xml in the etc/adminhtml and add a modifier pool in it.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
       <arguments>
           <argument name="modifiers" xsi:type="array">
               <item name="{identifier}" xsi:type="array">
                   <item name="class" xsi:type="string">{vendor}\{module}\Ui\DataProvider\Product\Form\Modifier\UpdateTierPricing</item>
                   <item name="sortOrder" xsi:type="number">200</item>
               </item>
           </argument>
       </arguments>
   </virtualType>
   <type name="{vendor}\{module}\Ui\DataProvider\Product\Form\Modifier\UpdateTierPricing">
       <arguments>
           <argument name="scopeName" xsi:type="string">product_form.product_form</argument>
       </arguments>
   </type>
</config>

Now, create a UpdateTierPricing.php at the Ui\DataProvider\Product\Form\Modifier and add the below code.

class UpdateTierPricing extends AbstractModifier
{
   /**
    * @var ArrayManager
    * @since 101.0.0
    */
   protected $arrayManager;

   /**
    * @var string
    * @since 101.0.0
    */
   protected $scopeName;

   /**
    * @var array
    * @since 101.0.0
    */
   protected $meta = [];

   /**
    * UpdateTierPricing constructor.
    * @param ArrayManager $arrayManager
    */
   public function __construct(
       ArrayManager $arrayManager
   ) {
       $this->arrayManager = $arrayManager;
   }

   /**
    * @param array $data
    * @return array
    * @since 100.1.0
    */
   public function modifyData(array $data)
   {
       // TODO: Implement modifyData() method.
       return $data;
   }

   /**
    * @param array $meta
    * @return array
    * @since 100.1.0
    */
   public function modifyMeta(array $meta)
   {
       // TODO: Implement modifyMeta() method.
       $this->meta = $meta;

       $this->customizeTierPrice();

       return $this->meta;
   }

   /**
    * @return $this
    */
   private function customizeTierPrice()
   {
       $tierPricePath = $this->arrayManager->findPath(
           ProductAttributeInterface::CODE_TIER_PRICE,
           $this->meta,
           null,
           'children'
       );

       if ($tierPricePath) {
           $this->meta = $this->arrayManager->merge(
               $tierPricePath,
               $this->meta,
               $this->getTierPriceStructure()
           );
       }

       return $this;
   }

   /**
    * @return array
    */
   private function getTierPriceStructure()
   {
       return [
           'children' => [
               'record' => [
                   'children' => [
                       {KEY} => [
                           'arguments' => [
                               'data' => [
                                   'config' => [
                                       'formElement' => Input::NAME,
                                       'componentType' => Field::NAME,
                                       'dataType' => Number::NAME,
                                       'label' => __('Label'),
                                       'dataScope' => '{KEY},
                                       'sortOrder' => 25,
                                   ],
                               ],
                           ],
                       ],
                   ],
               ],
           ],
       ];
   }
}

3. Override save & update handlers:

After performing the above steps we’ve added the field in the grid and the database. Now, we have to override the handlers using the preference.

To do so, we’ve to first create the SaveHandler.php in the Model directory. This file overrides the save functionality of tier price which will execute during the save the new product.

use Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\SaveHandler;

class SavePriceHandler extends SaveHandler
{

   /**
    * Get additional tier price fields.
    *
    * @param array $objectArray
    * @return array
    */
   public function getAdditionalFields(array $objectArray): array
   {
       $percentageValue = $this->getPercentage($objectArray);

       return [
           'value' => $percentageValue ? null : $objectArray['price'],
           'percentage_value' => $percentageValue ?: null,
           {KEY} => $this->getSecondaryUnit($objectArray),
       ];
   }

   /**
    * @param array $priceRow
    * @return mixed|null
    */
   public function getSecondaryUnit(array  $priceRow)
   {
       return isset($priceRow[{KEY}]) && !empty($priceRow[{KEY}])
           ? $priceRow[{KEY}]
           : null;
   }
}

To update the tier price data while update the product need to create the UpdateHandler.php in the Model directory and copy the code from vendor/magento/module-catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php and remove the updateValues function from the code and add below code in the file.

Note: Here, don’t forget to extend the UpdateHandler.

/**
* Update existing tier prices for processed product
*
* @param array $valuesToUpdate
* @param array $oldValues
* @return bool
*/
public function updateValues(array $valuesToUpdate, array $oldValues): bool
{

   $isChanged = false;
   foreach ($valuesToUpdate as $key => $value) {
       if ((!empty($value['value']) && (float)$oldValues[$key]['price'] !== (float)$value['value'])
           || $this->getPercentage($oldValues[$key]) !== $this->getPercentage($value)
           || $this->getSecondaryUnit($oldValues[$key]) !== $this->getSecondaryUnit($value)
       ) {
           $price = new \Magento\Framework\DataObject(
               [
                   'value_id' => $oldValues[$key]['price_id'],
                   'value' => $value['value'],
                   'percentage_value' => $this->getPercentage($value),
                   {KEY} => $this->getSecondaryUnit($value),
               ]
           );
           $this->tierPriceResource->savePriceData($price);
           $isChanged = true;
       }
   }

   return $isChanged;
}

/**
* Get additional tier price fields.
*
* @param array $objectArray
* @return array
*/
public function getAdditionalFields(array $objectArray): array
{
   $percentageValue = $this->getPercentage($objectArray);

   return [
       'value' => $percentageValue ? null : $objectArray['price'],
       'percentage_value' => $percentageValue ?: null,
       {KEY} => $this->getSecondaryUnit($objectArray),
   ];
}

/**
* @param array $priceRow
* @return mixed|null
*/
public function getSecondaryUnit(array  $priceRow)
{
   return isset($priceRow[{KEY}]) && !empty($priceRow[{KEY}])
       ? $priceRow[{KEY}]
       : null;
}

Create DataColumnUpdate.php in the Model directory. This file will load the saved data in the database.

class DataColumnUpdate extends Tierprice
{
   /**
    * @param array $columns
    * @return array
    */
   protected function _loadPriceDataColumns($columns)
   {
       $columns = parent::_loadPriceDataColumns($columns);
       $columns[{KEY}] = {KEY};
       return $columns;
   }
}

Now, you have to add the di.xml in the etc directory and add below code.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <preference for="Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\UpdateHandler"
               type="{vendor}\{module}\Model\UpdatePriceHandler" />
   <preference for="Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\SaveHandler"
               type="{vendor}\{module}\Model\Product\Attribute\Backend\TierPrice\SavePriceHandler" />
   <preference for="Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice"
               type="{vendor}\{module}\Model\ResourceModel\Product\Attribute\Backend\DataColumnUpdate" />
</config>

Here, we’ve added the all necessary code to override the tier pricing functionality. Now, just execute the below command from the Magento root directory and verify your implementation.

php bin/magento setup:upgrade
php bin/magento setup:di:compile

I hope this solution will resolve your problem. Let me know your thoughts in the comment section. And if you have any queries with this blog, I will be happy to solve that. Thanks and cheers … 🙂

Also read: Convert the price from one current to another in Magento 2

 

For better user experience, every business requires a search engine for an e-commerce site. The quick and relevant search result is the most important part of every search engine.

Nowadays, every user searches for their required product on the website and expects a relevant result. What happens if your search engine is unable to provide the relevant results? Will it abandon the site or will it try with different synonyms?

To resolve this issue, there are various open-source search engines available in the market but we’re picking the best search engines among them.

First of all, each search engine has its own benefits and can be utilized according to the requirements. To choose a search engine, we must think about the following three things.

  1. Catalog size: Size of catalog in your store.
  2. Indexing: Search engine capacity to handle a real time search.
  3. Memory: Server also matters for search engines. Because it requires memory to perform the operation.

Let’s overview some search engines.

  • Elasticsearch is currently at the top position among all the search engines. Elasticsearch has changed the performance limitations. It’s performance and relevance is much higher than all other search engines.
  • Elasticsearch also quickly updates the data and it’s very important with real-time indexing.
  • When the size of the database increases, it’s difficult to work with it but elastic search also scales up accordingly and it doesn’t impact the search result.
  • When you have a large catalog of approximately 60k+ then only you can feel the actual performance impact of elastic search more than the other search engines.
  • Elasticsearch also consumes more memory so, your server should be with high configuration.

Solr:

  • Solr stands at the second rank on the trending chart of search engines. It has its separate benefits from other search engines. If you want the functionality that your search engine can read the rich content like text, pdf documents then you must prefer the Solr search engine.
  • Solr stands at different positions because of its performance and features.
  • Solr has very great capabilities for faceted search. 
  • This search engine works best for the static data which are not frequently updating.

Sphinx:

  • The next search engine on my list is Sphinx. Sphinx is a very popular and experienced search engine. This search engine has powerful capabilities for the layered navigations due to its expertise and experiences.
  • Sphinx search supports the high scalability but not as compared to the elastic search.
  • Sphinx search consumes very less memory than other search engines. When your server is not with the high configuration then you must prefer this search engine.

Here, we can’t compare the above search engines because each has its individual feature which leads them in the market. Basically, When you have a very large catalog and high configured server then you must prefer the elastic search to see it’s extraordinary capabilities. But if your catalog size is small then you can choose the Sphinx/Solr.

Sphinx Search vs Elasticsearch

 

The main difference between Spinx search vs elasticsearch is— Sphinx Search needs managed schema files for defining data types, fields, and index structure whereas Elasticsearch doesn’t need index schema to index data and assign fields types. Sphinx is considered an open-source full-text search engine, whereas Elasticsearch is a tool in the “Search as a Service” category.

I hope you got a good understanding of the different kinds of Search Engines. Let me know your thoughts in the comment section. And if you have any queries with this blog I will be happy to solve that. Thanks and cheers … 🙂

FAQs

Which is better Elasticsearch or Solr?


You can choose Elasticsearch or Solr as both are leading search engines. Elasticsearch is faster and easier to use. Though it is heavier than Solr, the performance difference is worth it. On the other hand, Solr has more features and is better suited for complex queries.

Is Elasticsearch based on Solr?


No, Elasticsearch is not based on Solr. Both Elasticsearch and Solr are based on the same Java library Lucene and have several similar core features. But Solr is more focused on text-based queries and Elasticsearch works towards analytical queries. Their architecture and community support also vastly differ.

Does Amazon use Solr?


Yes, Amazon uses Solr. Amazon’s CloudSearch service is based on Solr to support full-text searches. It forms the search engine’s functionality by indexing and ranking search results. Though Amazon also uses other search engines, CloudSearch and in turn Solr forms an integral part of all Amazon apps.

Is Elasticsearch the best search engine?


Yes, Elasticsearch is the best search engine. Though other search engines are widely used, Elasticsearch’s speed, community support, flexibility, and scalability makes it popular. The search engine can index data within a second, and scale to multiple searches to accommodate petabytes of data. Elasticsearch is best suited for use cases where data updates continually.

Grow your online business like 4,112 subscribers

    * This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
    envelope

    Thank You!

    We are reviewing your submission, and will be in touch shortly.