:::: MENU ::::

Magento Tutorial | Magento Blog | Learn Magento 2

Are you a Magento 2 Developer? then you are at right place.

Cookies Consent Popup

If you are planning to add Recently Viewed products to your website then always use default magento approach. below approach is deprecated.


Because its not fully compatible with FPC so,now days magento using knockoutJS for Recently Viewed products. If you need to add a 'Recently Viewed' products block specifically via layout xml, use the same class Magento\Catalog\Block\Widget\RecentlyViewed which the 'Recently Viewed' Product widget uses.




This will render a simple grid with the last 4 products view by the user.
You can configure the block further, just as you would the widget instance:



One thing to note is that the argument values for ```show_attributes``` and ```show_buttons``` need to be declared as comma delimited strings (no spaces).
Declaring them as arrays or including spaces next to your comma's won't work due to the way the UI Component parse the content before Knockout renders it.
And since I said Knockout... it should play nicely with FPC, should.
Finally this solution doesn't use the **'action'** instruction which appears to have been deprecated.

That's it. Happy Coding !!!

First, You must have installed the Mailchimp extension in your project.


To Synchronize Mailchimp with Magento facilitate the subscription to the Newsletter for the customer and get the service of Mailchimp that will provide a lot of other additional services to the Magento platform.


How to configure Mailchimp in Magento 2?

1) Mailchimp -> Configuration,





2) From the Mailchimp General Configuration Section,
Choose Yes from the Enabled Field. You can see the above screenshot for reference to the enabled option.


3) Click on the Get API Credentials, and a new popup opens from Mailchimp,

When you click on the Get API Credentials button, a new popup will be displayed for asking the username and password for the MailChimp.
After Login with a Mailchimp account, the Second step you will ask to allow for Authorize Mailchimp for Magento 2, Press Allow button to go to the next step in the Popup.
Once you press Allow button, you will see the API key for Mailchimp that need to be added to the API key field of Magento.


You can see the final step of Popup from the Mailchimp API key as looks like the given screenshot.




4) After inserting the API key to the configuration, you need to click the top right button Save Config to save our changes.

This is the process to connect a Mailchimp account with Magento. you can use the subscription feature of Mailchimp in Magento by following the above steps.






 After upgrading to 2.4.3-p1 this issue occurs

Actually it was a problem with upgrade from 2.3.6 to 2.4.3 - magento added 2FA to login in 2.4 and on my Windows installation, for some reason, instead of showing error informing me that i need to configure 2FA to login, it just reloaded login form without any notice or errors.

Disabling 2FA module will solve the issue.


please run below command in terminal Magento root path :


    bin/magento module:disable Magento_TwoFactorAuth

    bin/magento cache:flush 

basically shim is used to avoid dependency conflict. 

For example if you calling a **custom javascript** then it must be loaded after javascript library isn't it? but magento doesn't know whether the library is loaded or not. So using shim we'll let system know that it is dependent on **library** so it will be instantiated when we map shim.

And

Magento uses requirejs to speed up the stuffs and (asynchronously module dependencies) nature of require js. So we use shim to tell the machine load our defined dependency first i.e., **jquery**

If we don't define then we get **jquery not defined error** while developing custom extensions as well.

The RequireJS shim configuration directive allows you to configure what I’ll call “load order” dependencies. i.e., you can say

when you load the jquery.cookie module? Make sure you’ve completely loaded the jquery module first. 


**Example :**

    var config = {

    paths:{

        "jquery.cookie":"Package_Module/path/to/jquery.cookie.min"

    },

    shim:{

        'jquery.cookie':{

            'deps':['jquery']

        }

    }};


We’ve defined a new top level configuration property named shim. This property is a javascript object of key value pairs. The key should be the name of your module (jquery.cookie above). The value is another javascript object that defines the shim configuration for this specific module.


That error indicates somewhere in your code passing null to the third parameter when calling a PHP function that is deprecated in PHP 8.1.


Assume you have below code:


return sprintf(
    $path,
    str_replace('methods_', '', $method)
);

The type of the third parameter should be changed to string if it is null. So the fixed code looks like the below:


return sprintf(
    $path,
    str_replace('methods_', '', $method ?? '')
);

The solution for your code:


$fromDate = date("Y-m-d",strtotime(str_replace("/", "-", $helper->getNewYearBallFromDate() ?? '')));
$toDate  = date("Y-m-d",strtotime(str_replace("/", "-", $helper->getNewYearBallToDate() ?? '')));

 Abstract Class:

  • An abstract class is a class that contains at least one abstract method. The abstract method is function declaration without anybody and it has the only name of the method and its parameters.
  • There can be any number of methods in the class and we have to declare the class as abstract only when there is an abstract method.

An abstract class is a class that is only partially implemented by the programmer. It may contain one or more abstract methods. An abstract method is simply a function definition that serves to tell the programmer that the method must be implemented in a child class.

Notes on Abstract class:

  • Objects cannot be created for the abstract classes.
  • If a class has only one method as abstract, then that class must be an abstract class. *The child class which extends an abstract class must define all the methods of the abstract class.
  • If the abstract method is defined as protected in the parent class, the function implementation must be defined as either protected or public, but not private.
  • The signatures of the methods must match, optional parameter given in the child class will not be accepted and error will be shown.

    Abstract classes that declare all their methods as abstract are not interfaces with different names. One can implement multiple interfaces, but not extend multiple classes (or abstract classes).

interface:

An interface is similar to an abstract class; indeed interfaces occupy the same namespace as classes and abstract classes. For that reason, you cannot define an interface with the same name as a class. An interface is a fully abstract class; none of its methods are implemented and instead of a class subclassing from it, it is said to implement that interface.

Rules of Interfaces:

  • All methods declared in an interface must be public; this is the nature of an interface.
  • All methods in the interface must be implemented within a class; failure to do so will result in a fatal error.
  • The class implementing the interface must use the exact same method signatures as are defined in the interface
  • Interfaces can be extended like classes using the extends operator.

Note on Interfaces:

  • We cannot create objects to interface, but the class implementing the interface can have objects.
  • We cannot define a variable in an interface.
  • If we extend interface all the methods of the interface must be implemented in the child class.
Abstract classInterface
It can have constants, members, method stubs (methods without a body), methodsIt can only have constants and methods stubs.
Methods and members can have public or protected visibilityMethods of interface should only be public not any other visibility
The concept of multiple inheritances not supportedAn interface can extend or a class can implement multiple other interfaces.
Child class must implement all the abstract method of parent class when extend keyword is used.No need of implementing methods from parent interface when interface is extending another interface

First you need to create your cron file in `Cron` directory as below


app/code/Vendor/Module/Cron/Mycron.php

    <?php

       namespace Vendor\Module\Cron;

       class Mycron

        {

          protected $logger;

          public function __construct(

        \Psr\Log\LoggerInterface $loggerInterface

          ) {

        $this->logger = $loggerInterface;

          }

          public function execute() {

            //Your Logic/Code here

            //$this->logger->debug('Vendor\Module\Cron\Mycron');

          }

        }


then create cron_groups.xml in `app/code/Vendor/Module/etc/cron_groups.xml.

 it's option step.


    <?xml version="1.0"?>

    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron_groups.xsd">

        <group id="vendor_module_cron_group">

            <schedule_generate_every>1</schedule_generate_every>

            <schedule_ahead_for>4</schedule_ahead_for>

            <schedule_lifetime>2</schedule_lifetime>

            <history_cleanup_every>10</history_cleanup_every>

            <history_success_lifetime>60</history_success_lifetime>

            <history_failure_lifetime>600</history_failure_lifetime>

            <use_separate_process>1</use_separate_process>

        </group>

    </config>


This will add entry in admin 

Now for scheduling cron script create crontab.xml on below path

`app/code/Vendor/Module/etc/crontab.xml`. 

Schedule time according to your need. I configured for every 5 minute.


    <?xml version="1.0"?>

    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">

        <group id="vendor_module_cron_group">

            <job name="vendor_module_cronjob_mycron" instance="Vendor\Module\Cron\Mycron" method="execute">

                <schedule>*/5 * * * *</schedule>

            </job>

        </group>

    </config>


This will execute your cron at every 5th min. your magento cron must be configured on your server or you can run manually by running `php bin/magento cron:run` (run twice for schedule and execute)


Note: you can skip cron_groups.xml step and define default group too as below 

<group id="default">


"catalog_product_save_before" This event gets called for every product save action, including new products.

In order to use it you could do the following in your module:

**app\code\Vendor\Module\etc\webapi_rest\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="catalog_product_save_before">

            <observer name="catalog_product_save_before_check_condition" instance="Vendor\Module\Observer\Productsaveafter" />

        </event>

    </config>


Then in `Productsaveafter.php` you can implement logic to connect to your own software and perform actions like adding or updating the product. 


Example:


**app\code\Vendor\Module\Observer\Productsaveafter.php**


    <?php

      namespace Vendor\Module\Observer;

     class Productsaveafter implements ObserverInterface{    

         public function execute(\Magento\Framework\Event\Observer $observer) {

           $product = $observer->getProduct();

            // Your logic to do stuff with $product       

            }

    }


Happy Coading ... Cheers !!!!!


Magento 2 Database Missing Table Error "inventory_stock_1"

Exception #0 (Zend_Db_Statement_Exception): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'inventory_stock_1' doesn't exist, query was: INSERT INTO `search_tmp_5c4f24124efa61_76233970` SELECT `main_select`.`entity_id`, SUM(score) AS `relevance` FROM (SELECT DISTINCT  `search_index`.`entity_id`, (((0) + (0)) * 1) AS `score` FROM `catalog_product_index_eav` AS `search_index`
 INNER JOIN `catalog_product_entity` AS `product` ON product.entity_id = search_index.entity_id
 INNER JOIN `inventory_stock_1` AS `stock_index` ON stock_index.sku = product.sku
 INNER JOIN `catalog_category_product_index_store1` AS `category_ids_index` ON search_index.entity_id = category_ids_index.product_id AND category_ids_index.store_id = '1' WHERE (search_index.store_id = '1') AND (`search_index`.`attribute_id` = 102 AND `search_index`.`value` in ('2', '4') AND `search_index`.`store_id` = '1') AND (category_ids_index.category_id = 394)) AS `main_select` GROUP BY `entity_id` ORDER BY `relevance` DESC, `entity_id` DESC
 LIMIT 10000
Exception #1 (PDOException): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'inventory_stock_1' doesn't exist

Solution:

Please follow these steps:

1) Open your store database.

2) Remove your current "inventory_stock_1" view and run this one:

CREATE
SQL SECURITY INVOKER
VIEW `inventory_stock_1`
  AS
    SELECT
    DISTINCT    
      legacy_stock_status.product_id,
      legacy_stock_status.website_id,
      legacy_stock_status.stock_id,
      legacy_stock_status.qty quantity,
      legacy_stock_status.stock_status is_salable,
      product.sku
    FROM `cataloginventory_stock_status` `legacy_stock_status`
      INNER JOIN `catalog_product_entity` product
        ON legacy_stock_status.product_id = product.entity_id;

 Reindex your store data then check your store.

This article is about Magento 2 – Update product attribute value . Updating product attribute value can be tricky sometimes. In this tutorial i will try to explain it swiftly and in a simple way. There can be various conditions in this matter. Like if someone wants to update the attribute values one by one or as a whole. Here, we are looking t update only one attribute value.

We can set all the values into one object (also we can use set for each attribute) & using set method we can save the product attribute with the help of productRepository or product model.

Furthermore When we use this method, there is a chance to get delays while updating the values like it may take 40 to 50 sec approx for one product . In our case we want to update only one attribute value. To render entire collection & updating the value might will take some ms delay.
So to update only one attribute value, we can do so by using the following code.

Consider the example here.

$item->setWidth(10);

$item->save();

We can use “updateAttributes” method to update Specific Attribute for product instead of updating all the update.

Here we have to pass 3 parameters.


Ex: $productIds , $attrData, $storeId
$objectManager->get(‘Magento\Catalog\Model\Product\Action’)

->updateAttributes( [$item],[‘width’ => 10],  $YourStoreID );

Similarly

$this->action->updateAttributes([$productObj->getId()], [‘Yourattribute_code’ => ‘Yourvalue’], $StoreId);

I am also providing the path for reference, it may vary depending upon your settings.

Magento\Catalog\Model\Product\Action

That’s it from this tutorial. I strongly believe there is always room for improvement.So i am open for any suggestion and feed back. Please feel free to leave hat you are thinking in the comments section below. Cheers.








Magento 2 versions from 2.3 have a replacement for traditional install/upgrade schema which is used to maintain the database structure.

From Magento 2 version 2.3, they have introduced the declarative schema file for database (etc/db_schema.xml) which is used to maintain the database structure for a module.

Now you can forget about untidy install/upgrade file and see the latest database structure version of a module in a single file.

For Magento 2 to identify the database schema changes, you need to maintain a file db_schema_whitelist.json against which Magento will compare your database structure from db_schema.xml and decide to update the database structure of your module while executing bin/magento setup:upgrade command.

In order to create the db_schema_whitelist.json in your Magento 2 module, you can run the below command:

  1. php bin/magento setup:db-declaration:generate-whitelist [options]

Where [options] will be,
–module-name[=Modulename] specifies the Module name to generate a whitelist.

Official Magento 2 documentation:

 If there is a repository and it does what you need well, always prefer the repository.


Repositories are part of the *Service Contracts* (they are implementations of interfaces in `Api`), this means they are meant as a public interface to other modules.


# Use Repositories for full loading


`$model->load()` is not part of the service contract. I had a question on that particular topic, you might find the answers useful: https://magento.stackexchange.com/questions/111286/is-there-ever-a-reason-to-prefer-model-load-over-service-contracts


# Use Factories to create new entities


Repositories do not come with methods to create a new entity, so in that case, you will need a factory. But use the factory for the *interface*, such as `Magento\Catalog\Api\Data\ProductInterfaceFactory` - it will create the right implementation based on DI configuration.


Then use the `repository->save()` method to save it.


# Use Collection Factories if you need more control


The following is not official Magento best practice, but currently, repositories do not give you fine control over what to load. The search criteria API lets you define filters, but for example, there is no way to select particular EAV attributes or specify which index tables to join. 


These are implementation details, hidden from the service contract APIs, but often these implementation details matter and you get poor performance if you ignore them. For that reason, as soon as the repositories are limiting me I don't hesitate anymore to use the underlying collections.

Try this code.

**system.xml**

    <field id="list_mode" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">

       <label>List Mode</label>        

       <source_model>Vendor\Module\Model\Config\Source\ListMode</source_model>

    </field>


**Vendor\Module\Model\Config\Source\ListMode.php**

  namespace Vendor\Module\Model\Config\Source;

    class ListMode implements \Magento\Framework\Data\OptionSourceInterface

    {

     public function toOptionArray()

     {

      return [

        ['value' => 'grid', 'label' => __('Grid Only')],

        ['value' => 'list', 'label' => __('List Only')],

        ['value' => 'grid-list', 'label' => __('Grid (default) / List')],

        ['value' => 'list-grid', 'label' => __('List (default) / Grid')]

      ];

     }

    }

            Try below code: 

           $obj = \Magento\Framework\App\ObjectManager::getInstance();    

            /** @var \Magento\Catalog\Model\Product $product */

            $productObject = $obj->get('Magento\Catalog\Model\Product');    

            $product = $productObject->loadByAttribute('sku', 'Test Test');    

            $linkDataAll = [];

            $skuLinks = "0012365,test1233,789456";

            $skuLinks = explode(",",$skuLinks);    

            foreach($skuLinks as $skuLink) {

                //check first that the product exist

                $linkedProduct = $productObject->loadByAttribute("sku",$skuLink);

                if($linkedProduct) {

                    /** @var  \Magento\Catalog\Api\Data\ProductLinkInterface $productLinks */

                    $productLinks = $obj->create('Magento\Catalog\Api\Data\ProductLinkInterface');

                    $linkData = $productLinks //Magento\Catalog\Api\Data\ProductLinkInterface

                        ->setSku($product->getSku())

                        ->setLinkedProductSku($skuLink)

                        ->setLinkType("related");

                    $linkDataAll[] = $linkData;

                }

            }

            if($linkDataAll) {

                print(count($linkDataAll)); //gives 3

                $product->setProductLinks($linkDataAll);

            }

            $product->save();

dont use **objectmanager** this code just for reference


You can use \Magento\InventoryApi\Api\SourceItemRepositoryInterface class with \Magento\Framework\Api\SearchCriteriaBuilder to get source item data by source code and product SKU.

Here are the sample model class


    <?php

    namespace MageExpert\Testing\Model;

    class SourceItemModel

    {

       

        private $searchCriteriaBuilder;

        private $sourceItemRepository;

        public function __construct(

            ...

            \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder,

            \Magento\InventoryApi\Api\SourceItemRepositoryInterface $sourceItemRepository

            ...

        ) {

            $this->searchCriteriaBuilder = $searchCriteriaBuilder;

            $this->sourceRepository = $sourceRepository;

        }

        public function getSourcesItems($souceCode, $sku)

        {

             $searchCriteria = $this->searchCriteriaBuilder

                ->addFilter('source_code', $souceCode)

                ->addFilter('sku', $sku)

                ->create();

            $sourceItemData = $this->sourceItemRepository->getList($searchCriteria);

            return $sourceItemData->getItems();

        }

    }


Now you can use getSourcesItems() function to get all source items by sources code


    $sourceCode = 'your_store_code';

    $sku = 'Product_1'

    $sourceItems = $this->getSourcesItems($sourceCode);

    foreach ($sourceItems as $sourceItem) {

        print_r($sourceItem->getData());

    }


OUTPUT:


    Array

    (

        [source_item_id] => 96

        [source_code] => your_store_code

        [sku] => Product_1

        [quantity] => 100.0000

        [status] => 1

    )