Magento 2 Demo Features: Overview

One of the things that put Magento® ahead of other CMS options is its secure network of community support and online learning resources. Magento has been designed so that it’s pretty intuitive to use. Plus, Magento is an open source, and anyone wanting to find out more about it has access to countless resources online. However, you need some time to learn how to set up certain settings and fill out your store with the information. Today, we’ll be showcasing the Magento 2 demo. This is a free online resource that lets business owners to explore the Magento 2 backend or interface with the frontend like a user. You can look into site features and functionality with sample information for a first-hand feel for how it works before finally selecting a CMS. Read on for a detailed look into the demo.

Magento Latest Version Features

The latest version of Magento 2 significant improvements in performance and security, as well as enhancements to shipping options, the dotmailer extension, and Amazon Pay features. More than 150 fixes have been included in the core code, and 25 improvements have been made to security. These improvements include refinements to the catalog indexing process, product sorting, and the checkout process.

Another benefit of the Magento community is that users can send feedback in and any bugs or usability improvements will be included in the next version. The current version features more than 350 adjustments based on user feedback. Several of the core-bundled extensions have been enhanced. Amazon Pay is now included in the payment options, and the current version of Magento features a payment provider gateway, making it easier than ever before to integrate your store with payment providers. The dotmailer extension has been updated for better GDPR compliance, including consent captures and warnings when you attempt to include non-subscribers on the account. These are just a few of the enhancements you’ll see when exploring the demo. Learn more about its features below.

Magento Demo Login

When you want to login to the Magento CMS demo, just follow the demo link and sign in as an admin. Take a moment to set up your username and password, and you’re in! The demo has sample information that can be worked with just as you will work with your live eCommerce site.

You can check out the frontend to see what your customers see when they visit the website or explore the backend, changing settings and configurations to see how these settings shift site functionality and customer experience.

Magento Demo Store Admin Panel

You’ll be directed to the opening page for the demo once you log in. The admin sidebar is along the left side of the screen. This is the central control panel for your site. This lets you navigate the entire backend of the site. Dashboard, System, Customers, Content, Marketing, Reports, Sales and Find Partner Extension. Here are some of the options and functionality of each section.

Dashboard

The Dashboard is the homepage of the Magento 2 test site. It features details regarding sales and customers. You can use this information to refine product listings, showcase bestsellers, and check sales within a given time frame. It provides an overview of company productivity and site success. The Dashboard also gives you access to information about last order, lifetime sales, and average orders.

There’s also a row of tabs that let you look into your new and existing customers, bestsellers and most viewed products. Plus, you can search in intervals of a day, a week, a month, one year, and two years. The Dashboard also features a notification icon in the top right of the page. This will let you keep up with any important changes or updates. It showcases the essential aspects of Magento functionality.

Sales

Sales Section

The Sales section will let you go in-depth when exploring Sales. There are seven options in the Sales menu:

  • Orders
  • Invoices
  • Shipments
  • Dispatches
  • Credit Memos
  • Billing Agreements
  • Transactions

The Order section is where you’ll go to input a new order. It keeps track of orders, shipping information and payment information, and the grand total. The Dispatches section shows the orders ready to ship. It helps you keep track of shipping schedules. It also lets you print dispatches for each order to go out. This makes paperwork easy. The invoices section can generate invoices and access all order information: customer names and dates and numbers of invoices and orders, to start. The transaction menu shows what’s happening with each sale being processed.

Magento’s Billing Agreement functionality makes checkout faster and easier by saving payment details for future orders. The Billings Agreement section keeps track of all billing agreements with each customer. When processing refunds, you can make a note in the files by going to the Credit Memos menu. Magento Wholesale Fast Order is featured on the frontend. This lets customers make repeat bulk purchases without having to enter each product individually.

Catalog Features

Catalog Section

The Catalog menu has been simplified in the new version. It now has only two selections: Products and Categories. In Products, you can add products and configure your product display. It also indicates the type of each product and lets you search by product type. The Categories section allows you to group products by type. It also generates meta-information automatically by product type. This section lets you control product listing and showcasing. The Categories menu lets you set up swatches and categories, create product grids, and arrange product listings.

Customer Segmentation

Customers Section

The Customers section offers basic client information and shopping history. Address, email, and phone number information are stored in each user file. These files show whether orders were made online or in a physical store. Order dates, amounts, and products for each order are included in this section. It lets you do customer segmentation, grouping customers with others that have similar shopping habits.

The Customers menu will also show when they first became customers and whether they are online at the moment or not. Customer files and new customers are automatically updated from orders on the frontend. They can also be added manually in the backend by admin.

Advanced Marketing Features

Marketing Features Section

The Marketing section offers key Magento features for helping customers find you. In one menu, manage email and newsletter templates, subscriber lists and queues. You can set up discounts, promotions, and pricing rules.

This is also the place to go to refine your SEO and search details. The Marketing section also allows you to manage user reviews and automate the sales process.

Content

Content Section

Moving on to the Content tab, this part of the backend allows you to manage the structure and aesthetics of your site. You can arrange pages, set up widgets, and manage the content of your blocks. It also lets you set up the design elements of your storefront. You can come here to change the website themes and configuration. You can also create a schedule for staging content updates at desired times.

Advanced Reporting

Advanced Reporting Section

In the Reports tab, you can find all the information you need, about any aspect of your company. You can find all the product information and automation details. You can explore sales and marketing details and access full reports on new customers, order counts, and order details. You can check into the totals for specific payment options, abandoned carts, and newsletter problem reports. The Advanced Reporting section also lets you explore details in even greater depth. Tax and billing regions, orders and revenue, everything is right at your fingertips. Check new customer accounts, coupon users, product wishlists, and a host of other details.

Stores

Stores Section

The Stores section is another improvement over Magento 1. It centralizes all store details on a single menu. You can come here to change your preferred currency and manage the currency rate. You can adjust your settings, establishing all the needed configurations in your storefront. This sets up all frontend actions to tailor site experience and eCommerce functionality. You can also pull up all the tax and shipping information and manage product attributes. This is where you go to find all tax rules, zones, and rates, shipping carriers and locations, and current terms and conditions.

Configuration

Your configuration is like the anatomy of your site. It lets you set up bootstrap parameters and tailor your command-line utilities like running indexes, disabling and enabling cache types and setting up translations. The Configuration menu also provides options for setting up themes or message queues, and for handling the production and storage sections. Payment options can be configured in the sales section of this menu and any settings changed here will be saved onto the site.

System

System Section

The System section lets you set up permissions and for all users, lock certain users out and establish user roles. You can come here to import adjustments to the website and export details to other platforms. Up to 25Mb can be transferred per import or export file. The import history of your site is saved, so you can keep track of all imported files. Tax rates can also be imported and exported between sites. In Systems, you can manage and integrate your extensions, set up extensions, and create backups. These are just a few of the features you can make use of. This page also links to Amazon logs and lets you manage encryption keys.

Tools

Tools Section

The System section also includes a collection of tools that come with Magento 2. They are great for managing your caches or indexes. You’ll probably want to refresh the cache after adding new configurations to make sure that they are locked in. Another useful tool is the backup function. Save copies of your site so that you can go back to an old version if needed. Finally, the Web Setup Wizard tool makes adding and remove modules a cinch. It also keeps track of which modules are dependent upon one another. It makes sure all supporting modules are enabled and prevents you from activating conflicting modules at once.

Magento 2 provides everything you need to keep track of your store and update your site. Plus its intuitive interface guarantees that site upkeep will be quick and simple. Its tools and features are great for midsize to big businesses.

Broken Magento Shopping Cart: Cause and Solution

Today, we will be examining the broken cart bug in Magento® 2. Below, you will see a step-by-step description of the Magento cart problem and a method for fixing it.

Description of the Problem

There is one Magento 2 bug inherited from Magento 1 that has not yet been fixed at the core level. Here is the gist of the bug: A customer goes to a site, logs in, and adds a product to their cart. Then, the customer may attempt to log in again after some time has passed. In Magento 1, the customer receives a message about a fatal error (administrators see a link to a report). The administrator will see the same fatal error if they want to edit or view this customer’s account. In Magento 2, the customer can’t add products to their cart and go to checkout, but static pages, categories, and product pages are available. When this happens, the server administrator sees the following message in the global error log:

[Mon Aug 06 16:49:26.148250 2018] [:error] [pid 10400] [client 127.0.0.1:49255] PHP Fatal error: Uncaught Error: Call to a member function getThumbnail() on null in /var/www/zenzii_beta/vendor/magento/module-configurable-product/CustomerData/ConfigurableItem.php:66\nStack trace:\n#0 /var/www/zenzii_beta/vendor/magento/module-checkout/CustomerData/DefaultItem.php(78): Magento\\ConfigurableProduct\\CustomerData\\ConfigurableItem->getProductForThumbnail()\n#1 /var/www/zenzii_beta/vendor/magento/module-checkout/CustomerData/AbstractItem.php(31): Magento\\Checkout\\CustomerData\\DefaultItem->doGetItemData()\n#2 /var/www/zenzii_beta/vendor/magento/framework/Interception/Interceptor.php(58): Magento\\Checkout\\CustomerData\\AbstractItem->getItemData(Object(Magento\\Quote\\Model\\Quote\\Item))\n#3 /var/www/zenzii_beta/vendor/magento/framework/Interception/Interceptor.php(138): Magento\\ConfigurableProduct\\CustomerData\\ConfigurableItem\\Interceptor->___callParent('getItemData', Array)\n#4 /var/www/zenzii_beta/vendor/magento/framework/Interception/Interceptor.php(153): Magento\\ConfigurableProduct\\CustomerData\\ConfigurableItem\\Intercep in /var/www/zenzii_beta/vendor/magento/module-configurable-product/CustomerData/ConfigurableItem.php on line 66, referer: http://zenzii-beta.loc/index.php/all-necklaceThis is the broken cart bug and one of Magento 2 issues. The following conditions are needed to reproduce it:
  1. The site must allow product configuration
  2. A logged-in customer must add a configured product to their cart
  3. The server administrator must delete the same product option (the simple product) that the customer added to their basket, and then the customer must log back into the site.

Below, we examine the reason for this bug using an example with Magento 2.2 code. We also examine a method for fixing it.

Why the Bug Occurs with Magento 2.2, and How to Fix It

When a user adds a configured product to their cart, two cart elements are actually added: the configured product and the simple product. In this case, the parent element is the configured product, and the hidden element is the simple product. Magento gets the weight, cost, and SKU from the simple product. What happens if the administrator deletes the simple product that the user has configured and added to their cart?

The following plugin is defined in the Magento_Quote core module

<type name="Magento\Catalog\Model\ResourceModel\Product">
    <plugin name="clean_quote_items_after_product_delete" type="Magento\Quote\Model\Product\Plugin\RemoveQuoteItems"/>
    <plugin name="update_quote_items_after_product_save" type="Magento\Quote\Model\Product\Plugin\UpdateQuoteItems"/>
</type>

Let’s examine the class of the clean_quote_items_after_product_delete plugin.

{
    /**
     * @var \Magento\Quote\Model\Product\QuoteItemsCleanerInterface
     */
    private $quoteItemsCleaner;
    /**
     * @param \Magento\Quote\Model\Product\QuoteItemsCleanerInterface $quoteItemsCleaner
     */
    public function __construct(\Magento\Quote\Model\Product\QuoteItemsCleanerInterface $quoteItemsCleaner)
    {
        $this->quoteItemsCleaner = $quoteItemsCleaner;
    }
    /**
     * @param ProductResource $subject
     * @param ProductResource $result
     * @param \Magento\Catalog\Api\Data\ProductInterface $product
     * @return ProductResource
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function afterDelete(
        ProductResource $subject,
        ProductResource $result,
        \Magento\Catalog\Api\Data\ProductInterface $product
    ) {
        $this->quoteItemsCleaner->execute($product);
        return $result;
    }
}

Note that until version 2.2, the around plugin was used instead of after, since after plugins didn’t support the input parameters of the original method. The \Magento\Quote\Model\Product\QuoteItemsCleanerInterface class looks as follows.

class QuoteItemsCleaner implements \Magento\Quote\Model\Product\QuoteItemsCleanerInterface
{
    /**
     * @var \Magento\Quote\Model\ResourceModel\Quote\Item
     */
    private $itemResource;
    /**
     * @param \Magento\Quote\Model\ResourceModel\Quote\Item $itemResource
     */
    public function __construct(\Magento\Quote\Model\ResourceModel\Quote\Item $itemResource)
    {
        $this->itemResource = $itemResource;
    }
    /**
     * {@inheritdoc}
     */
    public function execute(\Magento\Catalog\Api\Data\ProductInterface $product)
    {
        $this->itemResource->getConnection()->delete(
            $this->itemResource->getMainTable(),
            'product_id = ' . $product->getId()
        );
    }
}

As you can see, when the administrator deletes a product, the plugin simply deletes all the elements in the carts of all the users who had added the product. But the parent element isn’t deleted from the cart, and we end up with an orphaned item. To understand what is happening when trying to display the contents of a cart on the front end, we look at the code

namespace Magento\ConfigurableProduct\CustomerData;
use Magento\Catalog\Model\Config\Source\Product\Thumbnail as ThumbnailSource;
use Magento\Checkout\CustomerData\DefaultItem;
/**
 * Configurable item
 */
class ConfigurableItem extends DefaultItem
{
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $_scopeConfig;
    /**
     * @param \Magento\Catalog\Helper\Image $imageHelper
     * @param \Magento\Msrp\Helper\Data $msrpHelper
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool
     * @param \Magento\Checkout\Helper\Data $checkoutHelper
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Framework\Escaper|null $escaper
     */
    public function __construct(
        \Magento\Catalog\Helper\Image $imageHelper,
        \Magento\Msrp\Helper\Data $msrpHelper,
        \Magento\Framework\UrlInterface $urlBuilder,
        \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool,
        \Magento\Checkout\Helper\Data $checkoutHelper,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Framework\Escaper $escaper = null
    ) {
        parent::__construct(
            $imageHelper,
            $msrpHelper,
            $urlBuilder,
            $configurationPool,
            $checkoutHelper,
            $escaper
        );
        $this->_scopeConfig = $scopeConfig;
    }
    /**
     * {@inheritdoc}
     */
    protected function getProductForThumbnail()
    {
        /**
         * Show parent product thumbnail if it must be always shown according to the related setting in system config
         * or if child thumbnail is not available
         */
        $config = $this->_scopeConfig->getValue(
            \Magento\ConfigurableProduct\Block\Cart\Item\Renderer\Configurable::CONFIG_THUMBNAIL_SOURCE,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
        );
        $product = $config == ThumbnailSource::OPTION_USE_PARENT_IMAGE
            || (!$this->getChildProduct()->getThumbnail() || $this->getChildProduct()->getThumbnail() == 'no_selection')
            ? $this->getProduct()
            : $this->getChildProduct();
        return $product;
    }
    /**
     * Get item configurable child product
     *
     * @return \Magento\Catalog\Model\Product
     */
    protected function getChildProduct()
    {
        if ($option = $this->item->getOptionByCode('simple_product')) {
            return $option->getProduct();
        }
        return $this->getProduct();
    }
}

An attempt to retrieve the child product, which doesn’t exist, also results in this error. In fact, until version 2.1.3, a foreign key from the catalog_product_entity table was used instead of the plugin. This key deleted cart items for deleted products.

A fix for this error will be described below. The best option, in this case, is to delete the parent element from the cart along with the child. Unfortunately, you cannot use a foreign key to do this, since the child element is already linked to the parent by a foreign key. The solution that uses a MySQL trigger will also not work. MySQL doesn’t allow the same table to be used in the delete subquery, and it doesn’t allow deleting from the same table in the deletion trigger. This is because in such cases MySQL cannot determine the presence of circular references in queries, and it simply blocks them. Let’s add a trigger to the database:

DELIMITER //
create trigger quote_item_delete after deleting on quote_item for each row
begin
if OLD.parent_item_id is not NULL then
delete from quote_item where item_id = OLD.parent_item_id;
end if;

end;//
DELIMITER ;

Then, an attempt from the admin panel to delete the product will result in an error message for the administrator, and the following error will be added to the log: “SQLSTATE[HY000]: General error: 1442 Can’t update table ‘quote_item’ in stored function/trigger because it is already used by statement which invoked this stored function/trigger., query was:..”Therefore, you have to delete the parent element on the Magento side. Let’s create a module for this, which will consist of the following files.

registration.php

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Web4pro_BrokenCart',
    __DIR__
);
etc/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="Web4pro_BrokenCart" setup_version="0.0.1"></module>
</config>

etc/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">
    <preference for="Magento\Quote\Model\Product\QuoteItemsCleanerInterface" type="Web4pro\BrokenCart\Model\QuoteItemsCleaner" />
</config>

As you can see, we replaced the class for implementing the interface Magento\Quote\Model\Product\QuoteItemsCleanerInterface. The class will have the following implementation:

namespace Web4pro\BrokenCart\Model;
class QuoteItemsCleaner extends  \Magento\Quote\Model\Product\QuoteItemsCleaner
{
    /**
     * @var \Magento\Quote\Model\ResourceModel\Quote\Item
     */
    protected $itemResource;
    /**
     * @param \Magento\Quote\Model\ResourceModel\Quote\Item $itemResource
     */
    public function __construct(\Magento\Quote\Model\ResourceModel\Quote\Item $itemResource)
    {
        $this->itemResource = $itemResource;
        parent::__construct($itemResource);
    }
    public function execute(\Magento\Catalog\Api\Data\ProductInterface $product){
        $select = $this->itemResource->getConnection()->select();
        $select->from($this->itemResource->getMainTable(),'parent_item_id')
               ->where('parent_item_id is not NULL and product_id='.$product->getId());
        $parentItemIds = $this->itemResource->getConnection()->fetchCol($select);
        if(count($parentItemIds)){
            $this->itemResource->getConnection()
                 ->delete($this->itemResource->getMainTable(),'item_id in ('.implode(',',$parentItemIds).')');
        }
        return parent::execute($product);
    }
}

Website Launch Checklist: The Step by Step Process

Launching a new website can be a daunting task. And yet, it’s the first step for any online business owner. Here’s a website launch checklist to help you get your site up and running.So you want to build a website. It’s a critical step for any online shop and it has to be gotten right, but making a site can be a complicated process. It involves lots of steps and details. Choosing a CMS, performance optimization, navigation details, each element is critical for making your online store successful. Here are some tips to show you how to start a website and make it work.

What Do You Need to Make a Website?

A key element in exploring how to launch a website is to think about your goals. Do you want to start a business from the beginning? Maybe you want to expand your current business or to make a brand new site for it? Not only your basement, but it is also important to consider all the steps of making your site alive. You’ve come to the decision, but what’s the next? Look at the key points to consider to build a great site with the example of 3 CMS: Magento®, Drupal, and WordPress.

Choose the Right CMS for your site

Magento 2, WordPress, and Drupal are among the best CMS options. Magento 2 offers advanced product browsing and is both cross-browser and mobile compatible. It uses an intuitive interface and has excellent SEO and catalog management features. Drupal is module-based and easy to install, allowing unlimited customization. It is an excellent platform for organizing and categorizing products. WordPress is another common platform and it excels for simple sites. It offers a wide range of features and themes, extensive tutorials, and a dedicated support staff. Why do we consider them? Each solution fit certain business size and type of industry. So, it’s up to you what to choose. Below we will explain what features and benefits are included in each solution.

Make Sure It’s Secure

Site security is a key feature to consider. This protects customers and your site, and it makes the consumer feel safe entering their data for payment. So, use 24/7 monitoring scripts. Also, store all sensitive data, like website credentials, passwords, and customer information, in a secure database. Finally, it’s best to make copies of your site so that it can be recovered if any problems arise. A final copy should be made when the site is launched, and regular copies should be made with each further update.

While security is important regardless of the CMS, know that WordPress sites will require an extra measure of vigilance. Regarding Magento, it is highly secure and you can add more or fewer security features. Drupal also has basic security and advanced security that can be implemented with additional modules.

Website Performance

As customers navigate the site, they will first notice site performance. Once a site is optimized for performance, the web pages will load quickly and information will be cached to make the experience faster for repeat visitors. Decreasing lag time keeps customers engaged and increases conversions. You can test website performance with free online services like GT Metrix. There are also a number of plugins available to optimize web performance. Some popular choices are Autoptimize, WP Rocket, and Swift Performance. WordPress, Drupal, and Magento can be optimized with tools and right build website. However, Magento is the most robust solution, so it needs a bit more optimization work.

SEO Site Optimization

For an online store to be successful, it has to come up in a search. So, it needs SEO. Some tips: use unique page titles and meta descriptions. Keywords are important as well. Include no more than ten keywords in the copy for each page. Set up metadata for social media sharing content and RSS. Your website also needs an XML sitemap listed with search engines. Finally, URLs should be kept current and reflect page information. Online services like Woorank and Google Analytics will help you assess your SEO ranking. Several plugins for SEO are also available to up your site ranking. All of the platforms have basic SEO features to use for website’s visibility.

High Usability

Ease of use means customers will take more time on the site. It is easier to buy and conversion rates are higher. For the best usability, make the site easy to navigate and use an intuitive interface. Make sure the call-to-action buttons are simple to understand and marked clearly.

Intuitive Interface

An intuitive interface does what your customers expect. It makes sense, so users can go directly where they want on the site without having to spend time figuring out the navigation. To create an intuitive interface, put yourself in the client’s place. If this was your first visit, would it be easy to find and buy the product you’re looking for? Also, it is important to have simple navigation to each category and product, so your customers will find exactly what they want.

Familiar Icons

It is tempting to get fancy when creating the site. This is good for some things. Themes, embedded videos, a font that reflects your product. Other things, like cart and account icons, should be made how users have come to expect them. Use familiar buttons and icons. This makes it not confusing and time-saving for customers to take actions on your store.

Easy Navigation

Clear, simple navigation helps out in a couple of ways. First, it helps customers find products and information. Second, it improves the SEO ranking. Strong site navigation means that data can be effectively indexed by search engines. Here are some tips for easy navigation:

  • Include a search feature
  • Include ALT text with clickable images
  • Use accurate navigation titles
  • Incorporate clickable links in all navigation elements
  • Divide site categories clearly
  • Keep navigation consistent throughout the site

Useful Functionality

Functionality is the key to customer engagement. It’s the interactive aspect of the site. Site functionality of your site depends on your product and desired customers. Keep in mind the purpose of the site, and build the functionality to make that purpose happen. Try to let your customers reach the point of sale with a few clicks as possible. Keep it simple and direct, and treat customers as if they are valued guests. If we will compare Magento, Drupal and WordPress – each of them has different modules and extensions to add all necessary features to the store.

Filtering Products

To make your eCommerce site user-friendly, you’ll want to let customers search by price and type of product. Product filtering is a faceted search that lets customers find products with the qualities they want. It incorporates details like size, color, price, and reviews. Keep in mind that your site is performing a service. Design each feature to inform the customers and help them find what they want with a minimum of steps.

Reorder Feature

It’s a lot easier to develop a relationship with existing clients than to constantly seek out new ones. So, the reorder feature is essential. It helps previous customers place new orders without going through the hassle of entering payment information all over again. Remember, the fewer steps mean more conversions.

Availability and Out-of-Stock Features

This comes back to the choice of CMS. Each includes with different stock features and plugins. Consider the functionality and qualities your site needs. Then, check into the CMS to find out if they come standard with it. Remember that there is a difference between and feature bloat. It isn’t necessary to get every feature on the planet. Look for the ones that improve the customer’s experience.

Analytics

Analytics is a measurement of how customers work with the website. In-demand keywords, click-through rates, bounce rates, and conversions. Watching the analytics lets you refine your website based on what your customers want. Google offers free online services like Analytics, AdWords, and Webmaster to monitor site activity. Make sure to set up and sync them. Arrange funnels and goals in the analytic software, and make sure the website includes analytics codes.

GDPR Compliance

It is essential to protect clients’ information. GDPR establishes guidelines and laws to give people more control over personal information. Remember PCI compliance if your site processes credit cards. Make service terms, privacy policies, and cookie usage visible and clear. Consider clients with disabilities and make the site accessible for them. Several GDPR plugins and extensions are available to get your site GDPR compliant.

Clients’ Feedback

The success of an online shop is all about building relationships with clients. In providing them a chance to give feedback, you give them a voice. This lets your clients know you value them. Plus, the reviews, testimonials, and other information your clients provide give you valuable tips to refine your site.

In the end, we want to provide you the overall table of comparison of all the main characteristics to consider before the website launch. As it was mentioned earlier, we choose 3 well-known CMS: Magento, Drupal and WordPress and you can see their benefits by key parameters.

FunctionalityMagento 2DrupalWordPress
UsabilityAverage. You need a developer to set up all the features and customizations. However, the basic admin settings and admin panel usage are quite intuitive.Average. It requires a developer help.Simple. You will find everything on the admin panel.
SecurityHigh-level of security, including reCaptcha, Signifyd fraud protection tool and password management.High. Frequently update of modules and patches.Basic. You can use password management to protect your login.
Website PerformanceAverage. However, you can optimize it with Varnish page caching and admin rendering and other optimization techniques.Hight. Drupal is less resource intensive and it’s easy to support thousands of pages.Average. If you optimize your store promptly, you will have high-speed loading pages.
SEOHigh. You can add all the necessary information, Alt-text, meta tags, descriptions and use the full power of keywords and other SEO tools.High. There are all of the SEO features available with the additional modules and plenty of responsive themes for the sites.Basic. It offers several SEO tools to implement them for the store.
AnalyticsHigh. Magento has advanced reporting and also daily reports on a dashboard.Average. You can install Google Analytics for your site.Average. You can choose among various analytics tools on the plugins market.
GDPR ComplianceHigh, i.e., there are lots of GDPR extensions and also it is easy to make all the pop-ups and website sections to make your store compliant.Average. Drupal offers software tools for the GDPR regulation to implement them for the site. However, the most important documentation part is the responsibility of the store owner.High. Self-hosted WordPress.org sites have a built-in policy generator. Also, you can use various plugins to add some information if needed.
CustomizationHigh-level of scalability gives an ability to extend and add any different useful features for the store.High. It has a lot of extensive features and customizations.High. There are many templates and plugins available for free. That is why customization is easy.

How to Find a Magento Services Company That Cares

Magento® 2 is recognized as the leading platform in the field of eCommerce. It is scalable, customizable, and provides all the functionality suitable for any type of online business. Plus, Magento offers an excellent support community and top-notch security features. It’s the platform you want for building a new site, site maintenance, upgrades, or new extensions.

Now that you know you’re working with the best platform around, what’s next? Find a Magento developer! So, the question becomes, how can you find the best Magento services company for your site? More importantly, what qualities are needed in your developer to make your online shop the best that it can be? Let’s explore the criteria needed to choose the web agency that will bring your site to the top of the ranks.

Where to Start?

When choosing your Magento web agency, you want to make sure that they fit your specifications. The best web developer will be able to optimize your site for SEO and provide ongoing support and maintenance. The key is to do your homework. Look into the level of experience, both of the company and the specialists on their team. Explore the developer’s portfolio and all reviews and testimonials available. Finally, look into location and pricing. While you’ll want to make sure the project is within budget, it is equally important to ensure that you choose a developer that can make your site perfect.

Size of the Market

With the popularity of Magento 2, there are around a thousand companies on the market, serving more than a quarter million merchants worldwide. According to Magento website’s information, there are around 315,000 trained Magento developers. Some web agencies may not be reliable and others may be based in a different time zone or operate primarily in a different language. Some developers specialize in certain types of sites or businesses. With so many companies on the market, one of the best places to start is a research and review firm. Clutch is an excellent site for ratings and reviews of lead IT companies. It’s data-driven. The reviews are verified. It provides everything you need to separate the wheat from the chaff and find the best agency. You can check their top web development companies’ rank to filter the search results.

Market

Portfolio

Any developer worth their salt will have a long list of satisfied merchants that can attest to their skills and service. After narrowing down your selection, explore each company’s portfolio. You’ll find out how long they have been in business, what industries they’ve serviced, and the types of development work they’ve performed. Different companies will have experience building different custom extensions and creating specific functionalities. Visit the sites that the developer has built before to see their work in action.

Portfolio

Reviews

After checking the portfolio, look into testimonials and reviews. It’s important to hear what their previous customers have said about them. Other clients that have used the developer will be able to let you know how they deal with challenges or emergencies as they arise. They’ll be able to give reports on availability and communication. Also, each site has unique challenges, specific products, custom personalization, and functionalities. By looking into reviews, you’ll be able to see how the web agency has implemented these features for their other clients.

Location

Magento’s popularity means agencies and web developers can be discovered across the globe. So, location is one important consideration when choosing your Magento developer. Qualified developers located in different countries, but regional economy means that rates in some countries will be smaller. This can be appealing, especially if you’re on a tight budget. At the same time, you may need to contend with communication challenges or availability issues. When working with a developer in a different time zone, make sure that all your sites requirements are clearly laid out at the beginning. Also, some problems need prompt attention. You’ll want to ensure your web agency is available to address any problems within an acceptable timeframe.

Platform-Oriented

Each platform has its own unique coding requirements, extensions, features, and modules, features. While it’s nice to be a jack of all trades, you’ll do best with a developer that has mastered your platform. When using a Magento eCommerce platform, it’s best to seek out a web agency with a high degree of Magento expertise. While checking the company’s portfolio, research the platforms they’ve used. The web agency you want for your site will show a history of well-designed Magento sites with satisfied customers. This lets you know they’ve got the experience needed to make your site truly top notch.

Social Media Activity

In the digital era, social media has become an invaluable tool for finding the best agency. LinkedIn, Twitter, and Facebook is social networking sites that help customers and clients to come together. They provide excellent places to begin a search and allow you to explore customer feedback beyond what is presented on their website. When you look into a company’s social media site, you can find out how active they are. Learning about their activity in social media will help you to get the full picture of their professionalism. High activity means that the developer is up to date with the newest features, extensions, and upgrades.

Reliable Experience

When searching for a web agency, remember to look into all levels of Magento experience. This means that the agency should be certified by Magento. Quality assurance and project management in addition to frontend and backend site development will be a benefit. When vetting your developer, make sure to ask what kind of teams they have worked with and what industries they have designed for. Site execution and management are two essential areas. The site must be executed in the way that you want and need to get the best results. Finally, there will be times when it’s necessary to make changes and adjustments to the site. A professional company will be skilled and capable of making these changes when necessary.

Industry Expertise

In the vetting process, consider that each industry has a specific set of requirements for a website look and feel. You should look at the designs, themes, functionalities, and features that make a site aesthetic, profitable, and successful. Look for Magento services with industry-relevant experience. You want to find an agency that can tailor your site to specifications. If they have experience in the industry, they’ll know how to engage your customers and showcase your products. The right agency will show the expertise necessary to complete the project for your client.

Long-Term Partnership

When trying to find Magento certified developer, here’s a final consideration: longevity. Satisfied relationship means things are going well. This is a trustworthy sign of a solid and reliable working partner. When checking the portfolios of prospective web developers, look for those that have long-term partnerships with many digital and consulting agencies. This means that they can do the work and meet the needs of their clients over time.

Magento Freelancer or Agency: Who Will Suit Your Project?

One of the most important decision points for Magento® store owners is whether to go for a Magento freelancer or Magento development agency. As the operator or managers of an eCommerce store, you completely understand that the decision can get confusing at times. In this article, we will provide you with a comparative analysis about which choice is the suitable option for you.

Difference Between Magento Development Agency and Magento Freelancer

The pros of Magento freelancers are that you will get the flexibility that you need in your firm. Freelancers usually prefer to work alone and charge a comparatively lower price. They are usually experts in a particular field and are able to use specialized knowledge or skill for completing the work. We would recommend choosing an experienced Magento developer if your project is on a small scale level.

The cons of hiring a freelancer include the fact that they cannot be master of everything! Hence, they lack diversity when it comes to managing your website. The low operation cost and flexibility comes with a price as well. Since freelancers prefer to work during their own time, they tend to be unreliable at times and communication with them is less formalized.

The core competency of Magento development agency is the fact that they work in a team. The team provides diversity and the set of skill sets that you need for effective project management. Communicating work with them is actually much more convenient and easier. In usual cases, Magento development agencies have a separate client servicing department to deal with your feedback and queries.

Definitely going for Magento development agency is the better choice to go for. The price differs, but you will see it as more of an investment when you hire an experienced Magento 2 developer from agencies rather than an unnecessary cost.

Questions to Consider Before Making a Choice

When it comes to making a choice, it is easier to make the final call when you have some set standards of distinction or specific criteria to compare with. A Magento developer guide can be useful in this aspect. Some of the common questions you need to consider before making the choice are as follows:

  • What value will a Magento development agency or freelancer add to my website?
  • What are the pros and cons of each option?
  • What is the cost associated with a certain decision?
  • Will the undertaken cost be actually useful to my business?

Do You Need One Magento Developer or a Team?

Yes, Magento has a user-friendly interface, but a lot of back-end work goes for that kind of interface and development. The burning question here is whether you should opt for a developer or a team? The table below will help you better assess the situation.

Criteria of distinctionAgencyFreelancer
PricingMagento development cost of agencies is usually on the project basis. They charge around $8000 or more depending on the project size and type of work that should be done.The cost for freelancers is relatively cheaper and usually calculated on an hourly basis. The cost could start from $15 per hour to more depending on the experience of the freelancer.
KnowledgeIn agencies, there is a guarantee that you will find experienced Magento developers and a team with complementary skills.From country to country, the knowledge or level of expertise might vary for each freelancer.
SupportThe level of support is much more comprehensive for agencies. Constant communication and feedback is the key to building the right product. In fact, you will get the support of your store even after the development process will be finished. You may find a need in constant support and updates.With freelancers, it is difficult to provide constant feedback and get the work done on time specifically for complex projects.
Project ManagementAgencies are more prone to informing you on what stage of the project you are in. Project managers will help you with communication and timelines. Even if you hire a dedicated Magento developer, you will be provided with the updates during your collaboration.With freelancers, the communication channel will be optional. However, it is not a guarantee of constant communication and strict deadlines.
Additional ServicesWith migration, integration and customization support, it is always recommended that you choose an agency. Their team will have better knowledge and experience.Freelancers are known to be more reliable for performing standardized tasks rather than customized ones.

Where to Find Magento Developers?

Now that you understood that Magento developers can add a great deal of value to your business, here are tips about where to find a Magento developer.

Magento-Related Events

You will find a great networking opportunity in Magento related events and eCommerce conferences and seminars. Talking with professionals in the field will give you a better perspective.

Magento Certification Directory

Magento certification directory usually has the list of the skilled and experienced Magento developers. However, if you are looking for a full team, this is not the right place to search.

Research and Review Platforms

Review and research platforms such as Clutch, Angel.co, Extract is the best way to find eligible Magento development and eCommerce agencies. You will find honest reviews and area of expertise there which will help to make a conscious decision.

Freelance platforms

Platforms such as oDesk, Elance and much more will provide you with the right service you need. All you need to do is sign up there and post your project requirement and details. You will find a list of freelancers willing to do that job.

Social Media

LinkedIn, Twitter and Facebook can also be a good platform to look for eligible Magento developers.

How to Find the Best Magento Development Company

When you have an eCommerce business, one of the first things you have to consider is who you’re going to choose to develop your site. The developer or Magento® services company will determine how your site is built and executed. Still, this is a job that can be done by experienced developers.

To check if the company has a reliable experience, you need to look through:

  • The overall number of years in development
  • eCommerce expertise
  • Brand Reputation
  • Company’s presence on trusted listings and research & review platforms.

We’ve created the guide, which will aid you in filtering through the various number of companies worldwide.

Criteria for the Best Magento Development Company

Deciding which Magento web development company can be a time-consuming task for anyone. It’s hard to narrow down who you should hire, and it’s even harder to decide how you can narrow down the search field. There are a few criteria you can follow to ensure that your company gets the best it deserves.

Overall Years of eCommerce Experience

One of the best ways to find the right team for your eCommerce site is to find one with experience in the field. These people will be more prepared to deal with your site and any problems that can arise because they have knowledge not only in the development part but overall experience in this specific type of business field.

The more experience a company has, the more likely it has a strong brand reputation and reviews about the quality of its work. You’ll find people who have reviewed the company or posted comments about them. These reviews are important because they provide inside information into the development team you’re thinking of hiring.

Experienced Magento Developer in Team

When you want to hire a team to develop your site, you should look for a team that has an experienced Magento 2 developers among them. The overall experience of the developer should be more than 4 years and preferably Magento certification.

Types of Past Projects

Whoever you decide to hire should have a portfolio for you to look over. You can always ask to see this portfolio when you conduct an interview or when you send them a message. You should ask them what kind of companies they’ve worked with in the past, what kind of teams they’ve been a part of, etc.

Company’s Location

You can find Magento developers or agencies anywhere in the world. The rate depends on the country where a company is located and experience as we mentioned above. The only one thing that you should be aware of and consider before choosing the specific company is the time zone. If you use a dedicated approach or you hire offshore developers, it means that you will have a different time with them and the management should be explicitly planned. However, it is not an issue, if you hire a full team with project managers from the company’s side that will control the development process carefully. No matter where you choose to hire your developer on, you must ensure that terms are agreed upon.

Pricing Models

This is one of the most important features of anyone who owns a business. You have to figure out your pricing models, as well as how much you’re willing to pay. Next, you have to ask how much your site’s development is going to cost. There are a few models you can follow when it comes to the cost of your project.

Dedicated Team Model

Business owners love the dedicated team model. That’s because it usually involves a contract between the developer and the business owner. In this contract, specific terms are agreed upon.

Both parties have to agree on the requirements on the project as well as its length. Each dedicated team focuses entirely on projects for a single company before taking on new clients. If you go with this pricing model, you have the confidence in knowing that your company is the priority. Also, the huge benefit is that you take full or partial control over the project management.

Time & Material Model

The time and material model is popular among many contractors and developers. If you’re going to hire someone as your developer and you agree upon the time and material model, you can expect to pay for the developer’s time and the cost of his or her work.

Fixed Price Model

The fixed price model works exactly as its name suggests: you hire a developer for a project by the price agreed. This may include materials or it may not, but you don’t have to worry about being charged hourly or having hidden costs. The fixed price model can look different depending on what you and any other parties agree upon. You can be charged a fixed price every week, every two weeks, or every month.

QA And Testing

There are two components that are absolutely vital for the site future: your Q&A section of the site and the execution of your site.

The Q&A section of your eCommerce site will allow your potential customers to understand your business better. For those who aren’t sure if they want to choose a certain company, this section can be a deciding factor.

Next, you have to make sure that your site can be executed properly. When you’re going to hire a developer, you should consider asking them what their process is for making changes on your eCommerce site. Not only this, but you should ask if they have measures to reverse any changes in case something goes wrong. No matter what, hiring a team which is fully professional and have all the needed specialists will give you the best projects result at the end.

Availability of Support and Maintenance

After your eCommerce site is developed and deployed, you’ll need to have someone that can provide support and maintenance for your site. Your brand will develop over time, so you may need to consider hiring someone who can work for you over the long term. If the person developing your site isn’t willing to provide more long-term support, you may have to find someone who is. If you need someone to make changes to your live site, you may want to ask what their process is and whether they keep track of these changes.

10 Magento 2 eCommerce Themes: Best Selection of 2018

Magento® is one of the hottest eCommerce platforms on the market, and it’s easy to see why. Magento has a dedicated community of developers and a wide range of easily accessible documentation. Add to that a host of features like pre-coded templates, payment platforms, and themes to fit every online store, and it’s no wonder that Magento has gained popularity so quickly.

But there’s a downside to having many options. How do you know which one is right for your shop? So, to cut through the confusion here’s a breakdown of the top 10 Magento 2 themes. We’ll explore the features and pricing of each, plus service and which is most suitable for different kinds of online store.

What Is the Magento 2 Theme?

Essentially, a theme is an online storefront. It’s the layout of the page, how the menus are arranged, the color scheme, and the general aesthetic presentation. This may seem small, but developers know that the theme can make all the difference when it comes to bounce rates, conversion rates, and overall site success.

For example, you want a professional theme and one that reflects your product or service. Different colors and layouts are suitable for different business. A site that selling tech products will want a clean layout with colors that make your products stand out, while a fashion site needs to have a bit more flash to engage the customers.

Finally, the best themes work well with SEO and are technologically advanced, fitting for all mobile devices and browsers. This means that you’ll be looking for Magento 2 responsive themes capable of adjusting layouts to fit different screen sizes. The good news is that Magento offers themes suitable for all business needs. Let’s have a look at those that top the market for 2018.

Top Magento 2 Themes 2018

Here is a brief review of the best themes on the market this year. We highlight key features, pricing, service, and the niche they work best for. To explore any theme more fully, simply click the link in the heading.

Ultimo Responsive Magento Theme

Ultimo Magento Themes Ultimo is an ideal theme for those that are starting off. It can be customized easily and is extremely user-friendly. This means that it’s suitable for any type of online store. Ultimo also comes with an advanced admin module. This makes it simple to customize product categories based on certain search parameters. Plus, Ultimo is SEO optimized out-of-the-box. It can be installed in a couple of minutes and comes with free updates.

Some Key Features of Ultimo:

  • Multi-Store Ready
  • Responsive, Customizable Layout
  • Unlimited Colors and Custom Subthemes
  • High-Quality Brand Logos
  • More than 50 Content Placeholders
  • 12-Column Fluid Grid System

Ultimo is available for $99. This includes future updates, quality control checking by Envato, and 6 months of Infortis support. An additional $31.50 will extend the support to 12 months.

Fastest Multipurpose Responsive Theme

Fastest Magento Theme

This theme is aptly named – it was designed to be the fastest-loading theme on the market. It also provides a wide range of customizable options, making it suitable for any type of business. Fastest is loaded with extra features that come free with the package. From social sharing to brand tools, it provides everything you need to tailor the site to your product.

Some Key Features of Fastest:

  • Brand pages featuring SEO optimization, layer navigation, and social networking
  • Brand search boxes and brand list widgets
  • Brand logos, banners, and descriptions
  • Custom Ajax carts
  • Simple blog extensions
  • Pop-up newsletters and promo panels

Fastest can be purchased for $99. The package comes with free features like Ajax Layered Navigation and the Codazon slideshow extension. It also comes with 6 months free support from Codazon, quality control from Envato, and future updates. An additional six months of support can be purchased for $31.50.

Unero Minimalist Magento 2 and 1 Theme

Unero Minimalistic Magento Theme

Unero is designed to eliminate the non-essentials so that the products will stand out. This makes it suitable for a wide range of different businesses, including decoration, furniture, fashion, and more. It also has features which support an omnichannel approach, allowing customers to locate physical shops or buy directly from the site. Its streamlined layout makes site navigation easy, boosting customer engagement and increasing conversion rates.

Some Key Features of Unero:

  • MGS Frontend Builder
  • Simple blog extensions
  • 360-degree image view
  • Advanced Reports
  • Ajax Fly Cart
  • Social login

Like Ultimo and Fastest, the Unero theme costs only $99 and comes with Envato quality checking, 6months of free service (through ArrowHiTech), and future updates. Also like the themes above, service can be extended to 12 months for only $31.50.

Bencher Responsive Magento 1 & 2 Theme

Bencher Responsive ThemeBencher is another excellent theme all-around, good for any type of online store. It is designed to be easy to customize and responsive to every screen size and resolution. This makes it great for both PC and mobile devices. Bencher makes an excellent starter theme for unique sites and products, as it can be tailored to your exact specifications.

Some Key Features of Bencher:

  • Custom social media integration
  • Top Menu and Extra Menu
  • Custom Hover for product image
  • Untimated Colors Power Admin
  • Ajax cart
  • Simple Blog Extensions

Bencher is one of the least expensive themes in this list, able to be purchased for only $84. This package also includes future update, Envato quality checking, and six months of support through Alotheme. This support can be extended to twelve months with a payment of $25.88.

Hermes Premium Responsive Magento 2 & 1 Theme

Hermes Premium Responsive Theme

The adaptable, intuitive design of Hermes Magento theme makes it simple to customize. It includes six basic designs which can then be adjusted as needed to suit your store. This makes it yet another theme that works well with any type of business. It also means that your theme can be tailored with a minimum of time and effort. Hermes also has Lazy Loading so that it responds quickly and with a minimum of lag time.

Some Key Features of Hermes:

  • New Products Slider Extension
  • Smart Megamenu
  • Moo-Cloudzoom Product Image Zoom Extension
  • Categories Product Extension
  • Flexible styling, fonts, and colors
  • Ajax Layered Navigation

Hermes is the least expensive theme on this list, able to be purchased for only $55. The basic package includes six months of service by The_Blue_Sky, future updates, and Envato quality checking. The support can be extended to twelve months with no more than $15.

HippoSpa – SPA Store Magento Theme

Hippospa ThemeHippoSpa is a theme designed specifically for spa and wellness products. It combines eye-catching layouts with smooth performance and responsiveness. HippoSpa also specializes in SEO optimization, making it easy for customers to find your site. Add to that a clean content structure, two custom page designs, and extreme customizability, and you have a theme that can have your site up and look amazing in no time at all.

Some Key Features of HippoSpa:

  • Free assistance 24/7
  • SEO and Performance optimization
  • Cross-browser compatibility
  • Newsletter Pop-up
  • MegaMenu
  • Featured Products Theme Options

HippoSpa can be purchased for $179. Support is free, but other services will have to be bought individually. These include installation, which costs $49 and hosting, which costs $27. Additional options include the SEO boost for $99 and the 1-year SSL certificate for $10.

Pearl Responsive Theme

Pearl Magento 2 ThemePearl has been developed to be the ideal theme for fashion websites. The design focuses on elements that showcase your products in all their glory. Perfect spacing, parallax, sliders, modern lookbooks, the theme contains everything you need to engage customers with the image of the product. Pearl is also highly customizable, allowing you to create a unique appeal that reflects your brand.

Some Key Features of Pearl:

  • Plenty of frontend admin control options
  • Unlimited color options and Google fonts
  • Ajax Cart and Quick View
  • Flexible Header and Footer Options
  • Flexible Product Page Design
  • Google Analytics Advanced eCommerce Tag Manager

Pearl can be purchased for $499 and comes with three months of free service.

iShop Electronic Magento 2 Theme

Ishop Electronic Magento Theme

If you’re looking for a theme designed to support electronic and technological products, iShop is the perfect solution. It offers a clean layout and intuitive navigation, with features that showcase your products without crowding the page. iShop also offers full responsiveness, high performance, and extreme customizability. Its professional design helps to boost traffic and conversion rates for any online electronic store.

Some Key Features of iShop:

  • MegaMenu
  • Flexible Architecture
  • Cross-browser Compatibility
  • Social Options
  • Newsletter Pop-up
  • Google Web Fonts
  • Carousel Product Listing and Configurable Swatches

The iShop theme costs $179 and comes with free 24/7 access to a pro support team. Installation runs an additional $49, and hosting is provided for $27. Optional services like SSL certificates and SEO boosts can be purchased along with the theme for additional fees.

Magetique – AMP-Ready Magento 2 Theme

Magetique Premium Magento ThemeMagetique requires a minimum of programming skills or CSS knowledge to make a top-notch site. It’s also very versatile. The basic theme includes five layouts designed to fit virtually any online store. On-screen elements, pages blocks, and pages themselves can be edited easily and with a minimum of challenge. Plus, it’s fully responsive, loads quickly, and is optimized for mobile devices. Magetique is an excellent starter theme and can be customized and tailored to precise specifications.

Some Key Features of Magetique:

  • Intuitive Interface
  • Parallax and Video Background
  • Cross-browser Compatibility
  • MegaMenu
  • Social Options
  • Advanced Theme Options

Magetique can be purchased for $179 and offers 24/7 free support. Installation and hosting are extra: $27 for hosting and $49 for installation. A 1-year SSL certificate can be purchased for $10 dollars, and SEO optimization will cost an additional $99.

Recuidi Healthy Food Store Magento Theme

Recuidi Food Theme

The prime theme of 2018 for online healthy food stores is Recuidi. It offers a wide selection of theme options that are perfectly suited to showcase food with gorgeous colors and aesthetic presentation. Recuidi uses many banners and live-search functionality, making it easy for customers to find products, recipes, and other offerings quickly and with minimum effort. If you’re ready to set up an online organic food store or expand your physical store with online options, Recuidi offers a simple and professional solution.

Some Key Features of Recuidi:

  • MegaMenu
  • Newsletter Pop-up
  • Products Filter
  • Social Options
  • Features Products
  • Parallax and Video Background

Recuidi can be purchased for $179 and comes with free 24/7 service. Just as with iShop and Magetique, installation for Recuidi costs $49 and hosting can be bought for $27. Also as with the other themes, SEO and other options can be purchased for an additional charge.

Must-Have Magento Extensions to Spike Your Sales

If you own an eCommerce store you’ve probably used or have thought of using Magento® 2. This platform is known for its various benefits; including, the generation of sales and increased conversions. Today we will share top Magento extensions to increase your sales results.

What is Magento Extension?

Magento extensions are tools that allow you to enhance your Magento store. You can customize the site by installing extensions from the Magento extension store.

Magento Marketing Extensions

Among its many extensions, Magento offers marketing features that allow merchants to expand their marketing techniques, reach, etc. The method in which they do this depends entirely on the extension that’s being used. Below, you will find top extensions to increase the results from your marketing efforts.

Loyalty Program by Amasty

This extension is available with both the Community and the Enterprise editions. By using this marketing tool, merchants are able to create loyalty programs for their customers and clients. They can create rewards for those who receive points or who shop often.

A major benefit of using this extension is that merchants can choose among several promotional offers and it includes16 discount codes.

Coupon Link by Web Solutions NYC

By using Coupon Link, merchants are able to create links that are associated with coupon codes. When customers use these links, it provides them with a discount or coupon.

The way it works will depend on how you want to run your store. Most often, the URL is used in blog posts and marketing schemes.

MailBot by Beeketing

MailBot allows merchants to create pre-made campaigns. These include anything from scheduling emails or picking products to display. This is a benefit for anyone who is stressed on time because you can prepare all of your emails in advance to ensure the exact time of your email marketing campaign.

Gift Card by Aheadwords

This extension allows merchants to create gift cards for their customers and clients. The cards are customizable with this extension and the merchants can specify the values of the cards.

Clients and customers are able to use these cards to specify their own gift vouchers based on what their individual preferences are.

Subscribe at Checkout by Mageside

One of the most important factors in marketing is ensuring that your customers are subscribed to your newsletters. That’s because, after the initial sale, you can send them emails that will remind them you’re still around and that you have products that could be interesting for them.

With this extension, you can force your customers to subscribe by email before they checkout.

Rich Snippets by Atwix

The Rich Snippets extension allows you to show more detailed results. When your customers search for your products they’ll see more details about your every item.

Studies show that when eCommerce sites use Rich Snippets, their results tend to show up in higher positions. These results show up 20% to 30% more in increasing their ranking positions.

Even if you make everything to attract your customers with offers and provide them with your product updates, if you have some issues with navigation at your site, they will force difficulties when browsing around. That is why we suggest you the best extensions to improve the user experience at your store.

Best Free and Paid Magento 2 Extensions for Usability

If you’re looking for Magento eCommerce extensions to increase your store’s reach and usability, you might be wondering if there are any you can get without having to pay anything at all. That’s what we’re going to discuss in this section!

Improved Layered Navigation by Amasty

This Magento Commerce extension is one that many install in their Magento eCommerce store(s) because it allows customers to browse through your product catalog more comfortably.

The major benefit of using eCommerce extensions is that your customers have more access to your products. When they have this access to your products, they have the opportunity to buy them more easily.

Beyond this, you can also create the menu to suit your needs, as well as the needs of your clients. The SEO feature of this extension allows you to increase your ranking on Google because you can tailor your URLs and page names to include keywords.

AJAX Cart Pro for Magento 2 by Aheadwords

The AJAX Cart Pro for Magento 2 extension is one of the Magento ?ommunity extensions that allows merchants to create an “Add to cart” feature that immediately adds products from your website to their carts. These products can also have reviews and small descriptions added to them with this feature.

Request for Quote by FME

This is the perfect extension for anyone who wants to offer quotes to their clients. You can install fields for your customers or clients to fill out their personal information. Once they’ve done this, they have the opportunity to send the information to you in order to get an accurate quote or cost of the product.

Share Buttons by Neklo

Any merchant knows the importance of expanding their reach on social media. If you have Instagram, Twitter, Facebook, or Youtube pages, this widget tool allows you to share your site with your other websites. The Share Buttons extension also serves as a Magento blog extension.

Your customers can share your site to their own pages. This is the digital equivalent of word-to-mouth; only, hundreds of people can be exposed to your site at a time.

Detailed Product Review by MageWorkshop

When customers have a good or bad experience with your store, they might want to leave a detailed review about it. This allows potential customers who are using a Magento search module to get an idea about your products.

Development of Extensions

Now that you know about the various extensions available for Magento eCommerce business owners, you might be wondering about how extensions can influence the development of your site and how you can develop extensions on your site.

How to Install Plugin in Magento?

Installing a Magento plugin can be challenging; however, it is very manageable for all merchants when they use the following steps.

First, you should download or purchase the plugin you’re looking for. If a ZIP file is downloaded, you’ll have to unzip it. Some computers come with programs that automatically unzip folders when you try to open them.

Next, you’ll have to disable the Cache. Run the command and your extension will be installed from that moment. For the full guide, you can refer to our How to section.

How to Make Extension in Magento?

If you want to make your own extension in Magento, you’ll have to start out by disabling your Magento cache. Next, you’ll have to put Magento into developer mode by going to Magento 2 root in your terminal.

From here, you’ll have to create a Hello World module folder. You’ll also have to create a file called etc/module.xml, enable the module, and create a controller. This will give you an ability to create custom modules.

Managing the Magento 2.2.4 Category Tree Checkbox Bug

When using Magento® 2.2.4, a bug was identified in the category tree on the page for editing pricing rules. When trying to create pricing rules by product category (for a catalog or cart) not every category appeared in the category tree (a component of the form for generating a pricing rule for a category). More precisely, the root category of the first store, the first category of the store, and all of its child categories were displayed. Other categories were not displayed in the tree.

To fix this problem, we must analyze the implementation of the component, which you can see in the sample code in the next section.

Analysis of the Component for Price Rule by Product Category

Like the majority of Magento 2 components, this component has a back end and front end part (Javascript). A problem like this could be caused either by the fact that not all categories were transferred from the backend or by the fact that some categories weren’t rendered by the component in the Javascript tree for some reason. The component is implemented by the Magento\Catalog\Block\Adminhtml\Category\Checkboxes\Tree block. This block’s template, Magento_Catalog: catalog/category/checkboxes/tree.phtml, is set in its methods, and in it, the component is initialized by data in JSON format. The template looks like this in Magento 2.2.4:

<?php $_divId = 'tree-div_' . time() ?>
<div id="<?= /* @escapeNotVerified */ $_divId ?>" class="tree"></div>
<script id="ie-deferred-loader" defer="defer" src="//:"></script>
<script>
    require(["Magento_Catalog/js/category-checkbox-tree"], function (element) {
        element({
            "dataUrl":       "<?= /* @escapeNotVerified */ $block->getLoadTreeUrl() ?>" ,
            "divId":         "<?= /* @escapeNotVerified */$_divId ?>",
            "rootVisible":   <?php if ($block->getRoot()->getIsVisible()): ?>true<?php else : ?>false<?php endif; ?>,
            "useAjax":       <?= /* @escapeNotVerified */ $block->getUseAjax() ?>,
            "currentNodeId": <?= (int)$block->getCategoryId() ?>,
            "jsFormObject":  <?= /* @escapeNotVerified */ $block->getJsFormObject() ?>,
            "name":          "<?= /* @escapeNotVerified */ htmlentities($block->getRoot()->getName()) ?>",
            "checked":       "<?= /* @escapeNotVerified */ $block->getRoot()->getChecked() ?>",
            "allowDrop":     <?php if ($block->getRoot()->getIsVisible()): ?>true<?php else : ?>false<?php endif; ?>,
            "rootId":        <?= (int)$block->getRoot()->getId() ?>,
            "expanded":      <?= (int)$block->getIsWasExpanded() ?>,
            "categoryId":    <?= (int)$block->getCategoryId() ?>,
            "treeJson":      <?= /* @escapeNotVerified */ $block->getTreeJson() ?>
        });
    })
</s

As you can see, the Javascript component is implemented in a separate JS file: Magento_Catalog/js/category-checkbox-tree. This is an important difference from all previous versions since previously the Javascript of this component was implemented in the template, not put to a separate file. The code of this Javascript component is as follows:

define([
    'jquery',
    'prototype',
    'extjs/ext-tree-checkbox',
    'mage/adminhtml/form'
], function (jQuery) {
    'use strict';
    return function (config) {
        var tree,
            options = {
                dataUrl: config.dataUrl,
                divId: config.divId,
                rootVisible: config.rootVisible,
                useAjax: config.useAjax,
                currentNodeId: config.currentNodeId,
                jsFormObject: config.jsFormObject,
                name: config.name,
                checked: config.checked,
                allowDrop: config.allowDrop,
                rootId: config.rootId,
                expanded: config.expanded,
                categoryId: config.categoryId,
                treeJson: config.treeJson
            },
            data = {},
            parameters = {},
            root = {},
            len = 0,
            key = '',
            i = 0;
        /* eslint-disable */
        /**
         * Fix ext compatibility with prototype 1.6
         */
        Ext.lib.Event.getTarget = function (e) {// eslint-disable-line no-undef
            var ee = e.browserEvent || e;
            return ee.target ? Event.element(ee) : null;
        };
        /**
         * @param {Object} el
         * @param {Object} config
         */
        Ext.tree.TreePanel.Enhanced = function (el, config) {// eslint-disable-line no-undef
            Ext.tree.TreePanel.Enhanced.superclass.constructor.call(this, el, config);// eslint-disable-line no-undef
        };
        Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, {// eslint-disable-line no-undef
            /* eslint-enable */
            /**
             * @param {Object} config
             * @param {Boolean} firstLoad
             */
            loadTree: function (config, firstLoad) {// eslint-disable-line no-shadow
                parameters = config.parameters,
                data = config.data,
                root = new Ext.tree.TreeNode(parameters);// eslint-disable-line no-undef
                if (typeof parameters.rootVisible != 'undefined') {
                    this.rootVisible = parameters.rootVisible * 1;
                }
                this.nodeHash = {};
                this.setRootNode(root);
                if (firstLoad) {
                    this.addListener('click', this.categoryClick.createDelegate(this));
                }
                this.loader.buildCategoryTree(root, data);
                this.el.dom.innerHTML = '';
                // render the tree
                this.render();
            },
            /**
             * @param {Object} node
             */
            categoryClick: function (node) {
                node.getUI().check(!node.getUI().checked());
            }
        });
        jQuery(function () {
            var categoryLoader = new Ext.tree.TreeLoader({// eslint-disable-line no-undef
                dataUrl: config.dataUrl
            });
            /**
             * @param {Object} response
             * @param {Object} parent
             * @param {Function} callback
             */
            categoryLoader.processResponse = function (response, parent, callback) {
                config = JSON.parse(response.responseText);
                this.buildCategoryTree(parent, config);
                if (typeof callback === 'function') {
                    callback(this, parent);
                }
            };
            /**
             * @param {Object} config
             * @returns {Object}
             */
            categoryLoader.createNode = function (config) {// eslint-disable-line no-shadow
                var node;
                config.uiProvider = Ext.tree.CheckboxNodeUI;// eslint-disable-line no-undef
                if (config.children && !config.children.length) {
                    delete config.children;
                    node = new Ext.tree.AsyncTreeNode(config);// eslint-disable-line no-undef
                } else {
                    node = new Ext.tree.TreeNode(config);// eslint-disable-line no-undef
                }
                return node;
            };
            /**
             * @param {Object} parent
             * @param {Object} config
             * @param {Integer} i
             */
            categoryLoader.processCategoryTree = function (parent, config, i) {// eslint-disable-line no-shadow
                var node,
                    _node = {};
                config[i].uiProvider = Ext.tree.CheckboxNodeUI;// eslint-disable-line no-undef
                _node = Object.clone(config[i]);
                if (_node.children && !_node.children.length) {
                    delete _node.children;
                    node = new Ext.tree.AsyncTreeNode(_node);// eslint-disable-line no-undef
                } else {
                    node = new Ext.tree.TreeNode(config[i]);// eslint-disable-line no-undef
                }
                parent.appendChild(node);
                node.loader = node.getOwnerTree().loader;
                if (_node.children) {
                    categoryLoader.buildCategoryTree(node, _node.children);
                }
            };
            /**
             * @param {Object} parent
             * @param {Object} config
             * @returns {void}
             */
            categoryLoader.buildCategoryTree = function (parent, config) {// eslint-disable-line no-shadow
                if (!config) {
                    return null;
                }
                if (parent && config && config.length) {
                    for (i = 0; i < config.length; i  ) {
                        categoryLoader.processCategoryTree(parent, config, i);
                    }
                }
            };
            /**
             *
             * @param {Object} hash
             * @param {Object} node
             * @returns {Object}
             */
            categoryLoader.buildHashChildren = function (hash, node) {// eslint-disable-line no-shadow
                // eslint-disable-next-line no-extra-parens
                if ((node.childNodes.length > 0) || (node.loaded === false && node.loading === false)) {
                    hash.children = [];
                    for (i = 0, len = node.childNodes.length; i < len; i  ) {
                        /* eslint-disable */
                        if (!hash.children) {
                            hash.children = [];
                        }
                        /* eslint-enable */
                        hash.children.push(this.buildHash(node.childNodes[i]));
                    }
                }
                return hash;
            };
            /**
             * @param {Object} node
             * @returns {Object}
             */
            categoryLoader.buildHash = function (node) {
                var hash = {};
                hash = this.toArray(node.attributes);
                return categoryLoader.buildHashChildren(hash, node);
            };
            /**
             * @param {Object} attributes
             * @returns {Object}
             */
            categoryLoader.toArray = function (attributes) {
                data = {};
                for (key in attributes) {
                    if (attributes[key]) {
                        data[key] = attributes[key];
                    }
                }
                return data;
            };
            categoryLoader.on('beforeload', function (treeLoader, node) {
                treeLoader.baseParams.id = node.attributes.id;
            });
            /* eslint-disable */
            categoryLoader.on('load', function () {
                varienWindowOnload();
            });
            tree = new Ext.tree.TreePanel.Enhanced(options.divId, {
                animate: false,
                loader: categoryLoader,
                enableDD: false,
                containerScroll: true,
                selModel: new Ext.tree.CheckNodeMultiSelectionModel(),
                rootVisible: options.rootVisible,
                useAjax: options.useAjax,
                currentNodeId: options.currentNodeId,
                addNodeTo: false,
                rootUIProvider: Ext.tree.CheckboxNodeUI
            });
            tree.on('check', function (node) {
                options.jsFormObject.updateElement.value = this.getChecked().join(', ');
                varienElementMethods.setHasChanges(node.getUI().checkbox);
            }, tree);
            // set the root node
            //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
            parameters = {
                text: options.name,
                draggable: false,
                checked: options.checked,
                uiProvider: Ext.tree.CheckboxNodeUI,
                allowDrop: options.allowDrop,
                id: options.rootId,
                expanded: options.expanded,
                category_id: options.categoryId
            };
            //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
            tree.loadTree({
                parameters: parameters, data: options.treeJson
            }, true);
            /* eslint-enable */
        });
    };
});

As you can see, this component is a Javascript function that initializes an object (the category tree) and also auxiliary objects, which allow to display a category tree with checkboxes in the browser. The libraries jQuery, prototype, and extjs are used to do this. The last two of these have been used since Magento 1.

Using the Javascript browser debugger, I was able to establish that the category tree from the back end is completely transferred and the problem with rendering occurs at the Javascript component level. The location of the error was established, after detailed analysis, to be the buildCategoryTree method of the categoryLoader object. In the component, it looks as follows:

categoryLoader.buildCategoryTree = function (parent, config) {// eslint-disable-line no-shadow
    if (!config) {
        return null;
    }
    if (parent && config && config.length) {
        for (i = 0; i < config.length; i  ) {
            categoryLoader.processCategoryTree(parent, config, i);
        }
    }
};

The problem was in the loop counter variable i. The developers of the last version of the Magento_Catalog module made the variable global within the whole component. As a result, after rendering all of the first category’s child categories, of which there were 42, the cycle was complete. This is because there were far fewer subcategories in the root category, and they were not rendered. The variable should have been made locally in the buildCategoryTree method. It then would have been visible in the processCategoryTree method as well, since it would have been passed as a parameter. In fact, in the component’s implementation in previous versions, this variable was local, which you can see below (in an example from Magento 2.2.3):

categoryLoader.buildCategoryTree = function(parent, config)
    {
        if (!config) return null;

        if (parent && config && config.length){
            for (var i = 0; i < config.length; i  ) {
                config[i].uiProvider = Ext.tree.CheckboxNodeUI;
                var node;
                var _node = Object.clone(config[i]);
                if (_node.children && !_node.children.length) {
                    delete(_node.children);
                    node = new Ext.tree.AsyncTreeNode(_node);
                } else {
                    node = new Ext.tree.TreeNode(config[i]);
                }
                parent.appendChild(node);
                node.loader = node.getOwnerTree().loader;
                if (_node.children) {
                    this.buildCategoryTree(node, _node.children);
                }
            }
        }
    };

The reason for this bug was established, but another problem arose while attempting to fix it. All of the component’s objects exist only in local variables within the component’s functions and are not visible externally. Passing them into the component of some object that can call external code within the component is also not possible because the component doesn’t call external callback functions and doesn’t throw events. A mixin can’t be created because the component is a Javascript function and doesn’t have methods or properties available from outside.

Ultimately, we decided to completely rewrite the component, although that only required changing one line. This is better than editing the component in the core module. You must replace the template that initializes the component, and you must replace the existing component call in the template with the correct one.

Changes to the Component Code for Fixing the Category Display Problem

Since we’re dealing with a block of the admin panel, the adminhtml_block_html_before event handler can be used, which almost all admin panel blocks call before output. We implement this:

<event name="adminhtml_block_html_before">
    <observer name="default_product" instance="Web4pro\Defaultproduct\Observer\Changetemplate" shared="false" />
</event>
class Changetemplate implements \Magento\Framework\Event\ObserverInterface {

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

        if($block = $observer->getEvent()->getBlock()){
.
           if($block instanceof \Magento\Catalog\Block\Adminhtml\Category\Checkboxes\Tree){
               $block->setTemplate('Web4pro_Defaultproduct::tree.phtml');
           }
        }
    }
}

The new template will look as follows:

<?php $_divId = 'tree-div_' . time() ?>
<div id="<?= /* @escapeNotVerified */ $_divId ?>" class="tree"></div>
<script id="ie-deferred-loader" defer="defer" src="//:"></script>
<script>
    require(["Web4pro_Defaultproduct/js/category-checkbox-tree"], function (element) {
        element({
            "dataUrl":       "<?= /* @escapeNotVerified */ $block->getLoadTreeUrl() ?>" ,
            "divId":         "<?= /* @escapeNotVerified */$_divId ?>",
            "rootVisible":   <?php if ($block->getRoot()->getIsVisible()): ?>true<?php else : ?>false<?php endif; ?>,
            "useAjax":       <?= /* @escapeNotVerified */ $block->getUseAjax() ?>,
            "currentNodeId": <?= (int)$block->getCategoryId() ?>,
            "jsFormObject":  <?= /* @escapeNotVerified */ $block->getJsFormObject() ?>,
            "name":          "<?= /* @escapeNotVerified */ htmlentities($block->getRoot()->getName()) ?>",
            "checked":       "<?= /* @escapeNotVerified */ $block->getRoot()->getChecked() ?>",
            "allowDrop":     <?php if ($block->getRoot()->getIsVisible()): ?>true<?php else : ?>false<?php endif; ?>,
            "rootId":        <?= (int)$block->getRoot()->getId() ?>,
            "expanded":      <?= (int)$block->getIsWasExpanded() ?>,
            "categoryId":    <?= (int)$block->getCategoryId() ?>,
            "treeJson":      <?= /* @escapeNotVerified */ $block->getTreeJson() ?>
        });
    })
<

The new component will look as follows:

define([
    'jquery',
    'prototype',
    'extjs/ext-tree-checkbox',
    'mage/adminhtml/form'
], function (jQuery) {
    'use strict';
    return function (config) {
        var tree,
            options = {
                dataUrl: config.dataUrl,
                divId: config.divId,
                rootVisible: config.rootVisible,
                useAjax: config.useAjax,
                currentNodeId: config.currentNodeId,
                jsFormObject: config.jsFormObject,
                name: config.name,
                checked: config.checked,
                allowDrop: config.allowDrop,
                rootId: config.rootId,
                expanded: config.expanded,
                categoryId: config.categoryId,
                treeJson: config.treeJson
            },
            data = {},
            parameters = {},
            root = {},
            len = 0,
            key = '',
            i = 0;
        /* eslint-disable */
        /**
         * Fix ext compatibility with prototype 1.6
         */
        Ext.lib.Event.getTarget = function (e) {// eslint-disable-line no-undef
            var ee = e.browserEvent || e;
            return ee.target ? Event.element(ee) : null;
        };
        /**
         * @param {Object} el
         * @param {Object} config
         */
        Ext.tree.TreePanel.Enhanced = function (el, config) {// eslint-disable-line no-undef
            Ext.tree.TreePanel.Enhanced.superclass.constructor.call(this, el, config);// eslint-disable-line no-undef
        };
        Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, {// eslint-disable-line no-undef
            /* eslint-enable */
            /**
             * @param {Object} config
             * @param {Boolean} firstLoad
             */
            loadTree: function (config, firstLoad) {// eslint-disable-line no-shadow
                parameters = config.parameters,
                data = config.data,
                root = new Ext.tree.TreeNode(parameters);// eslint-disable-line no-undef
                if (typeof parameters.rootVisible != 'undefined') {
                    this.rootVisible = parameters.rootVisible * 1;
                }
                this.nodeHash = {};
                this.setRootNode(root);
                if (firstLoad) {
                    this.addListener('click', this.categoryClick.createDelegate(this));
                }
                this.loader.buildCategoryTree(root, data);
                this.el.dom.innerHTML = '';
                // render the tree
                this.render();
            },
            /**
             * @param {Object} node
             */
            categoryClick: function (node) {
                node.getUI().check(!node.getUI().checked());
            }
        });
        jQuery(function () {
            var categoryLoader = new Ext.tree.TreeLoader({// eslint-disable-line no-undef
                dataUrl: config.dataUrl
            });
            /**
             * @param {Object} response
             * @param {Object} parent
             * @param {Function} callback
             */
            categoryLoader.processResponse = function (response, parent, callback) {
                config = JSON.parse(response.responseText);
                this.buildCategoryTree(parent, config);
                if (typeof callback === 'function') {
                    callback(this, parent);
                }
            };
            /**
             * @param {Object} config
             * @returns {Object}
             */
            categoryLoader.createNode = function (config) {// eslint-disable-line no-shadow
                var node;
                config.uiProvider = Ext.tree.CheckboxNodeUI;// eslint-disable-line no-undef
                if (config.children && !config.children.length) {
                    delete config.children;
                    node = new Ext.tree.AsyncTreeNode(config);// eslint-disable-line no-undef
                } else {
                    node = new Ext.tree.TreeNode(config);// eslint-disable-line no-undef
                }
                return node;
            };
            /**
             * @param {Object} parent
             * @param {Object} config
             * @param {Integer} i
             */
            categoryLoader.processCategoryTree = function (parent, config, i) {// eslint-disable-line no-shadow
                var node,
                    _node = {};
                config[i].uiProvider = Ext.tree.CheckboxNodeUI;// eslint-disable-line no-undef
                _node = Object.clone(config[i]);
                if (_node.children && !_node.children.length) {
                    delete _node.children;
                    node = new Ext.tree.AsyncTreeNode(_node);// eslint-disable-line no-undef
                } else {
                    node = new Ext.tree.TreeNode(config[i]);// eslint-disable-line no-undef
                }
                parent.appendChild(node);
                node.loader = node.getOwnerTree().loader;
                if (_node.children) {
                    categoryLoader.buildCategoryTree(node, _node.children);
                }
            };
            /**
             * @param {Object} parent
             * @param {Object} config
             * @returns {void}
             */
            categoryLoader.buildCategoryTree = function (parent, config) {// eslint-disable-line no-shadow
                if (!config) {
                    return null;
                }
                if (parent && config && config.length) {
                    for (var i = 0; i < config.length; i  ) {
                        categoryLoader.processCategoryTree(parent, config, i);
                    }
                }
            };
            /**
             *
             * @param {Object} hash
             * @param {Object} node
             * @returns {Object}
             */
            categoryLoader.buildHashChildren = function (hash, node) {// eslint-disable-line no-shadow
                // eslint-disable-next-line no-extra-parens
                if ((node.childNodes.length > 0) || (node.loaded === false && node.loading === false)) {
                    hash.children = [];
                    for (i = 0, len = node.childNodes.length; i < len; i  ) {
                        /* eslint-disable */
                        if (!hash.children) {
                            hash.children = [];
                        }
                        /* eslint-enable */
                        hash.children.push(this.buildHash(node.childNodes[i]));
                    }
                }
                return hash;
            };
            /**
             * @param {Object} node
             * @returns {Object}
             */
            categoryLoader.buildHash = function (node) {
                var hash = {};
                hash = this.toArray(node.attributes);
                return categoryLoader.buildHashChildren(hash, node);
            };
            /**
             * @param {Object} attributes
             * @returns {Object}
             */
            categoryLoader.toArray = function (attributes) {
                data = {};
                for (key in attributes) {
                    if (attributes[key]) {
                        data[key] = attributes[key];
                    }
                }
                return data;
            };
            categoryLoader.on('beforeload', function (treeLoader, node) {
                treeLoader.baseParams.id = node.attributes.id;
            });
            /* eslint-disable */
            categoryLoader.on('load', function () {
                varienWindowOnload();
            });
            tree = new Ext.tree.TreePanel.Enhanced(options.divId, {
                animate: false,
                loader: categoryLoader,
                enableDD: false,
                containerScroll: true,
                selModel: new Ext.tree.CheckNodeMultiSelectionModel(),
                rootVisible: options.rootVisible,
                useAjax: options.useAjax,
                currentNodeId: options.currentNodeId,
                addNodeTo: false,
                rootUIProvider: Ext.tree.CheckboxNodeUI
            });
            tree.on('check', function (node) {
                options.jsFormObject.updateElement.value = this.getChecked().join(', ');
                varienElementMethods.setHasChanges(node.getUI().checkbox);
            }, tree);
            // set the root node
            //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
            parameters = {
                text: options.name,
                draggable: false,
                checked: options.checked,
                uiProvider: Ext.tree.CheckboxNodeUI,
                allowDrop: options.allowDrop,
                id: options.rootId,
                expanded: options.expanded,
                category_id: options.categoryId
            };
            //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
            tree.loadTree({
                parameters: parameters, data: options.treeJson
            }, true);
            /* eslint-enable */
        });
    };
});

As you can see, it differs from the original by only one line. However, now all categories created are available when editing the pricing rule.

Amasty_Rewards 1.5.0. Fixing a Checkout Bug

Amasty provides a useful module, Amasty_Rewards, for store loyalty program. This extension lets administrators ability to configure rules for how customers will accrue Reward Points (usually for previously created orders). Customers can use these points to get discounts on future orders.

Let’s say you have a site on Magento® 2 and this module has been working since its first version. After upgrading from Amasty 1.2.2 to 1.5.0, you’re pleased to see a new component for using points on the checkout page. Before, a customer could only set the desired number of points on the cart page, and a custom component was implemented for checkout. A custom component was immediately deleted since it would be no longer needed. However, a bug was identified in the component from Amasty. We describe it below.

Let’s say a customer has 100 points. In the configuration, a Point Rate of 20 is set, meaning 20 points translate to a $1 discount, and 1 point gives the customer a $0.05 discount. After submitting a form (using AJAX), the number of points in the form is changed to 0.05, and the text below the input field incorrectly says, “You have 99.95 points left,” while the discount amount is calculated correctly as $0.05.

Next, we will determine the cause of the bug and show how to fix it.

Investigating the Reason for the Bug at Checkout

The following was established after analyzing the component. The component itself is defined as follows:

<referenceBlock name="checkout.root">
    <arguments>
        <argument name="jsLayout" xsi:type="array">
            <item name="components" xsi:type="array">
                <item name="checkout" xsi:type="array">
                    <item name="children" xsi:type="array">
                        <item name="steps" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="billing-step" xsi:type="array">
                                    <item name="component" xsi:type="string">uiComponent</item>
                                    <item name="children" xsi:type="array">
                                        <item name="payment" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="afterMethods" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="rewards" xsi:type="array">
                                                            <item name="component" xsi:type="string">Amasty_Rewards/js/view/checkout/payment/rewards</item>
                                                            <item name="sortOrder" xsi:type="string">10</item>
                                                            <item name="children" xsi:type="array">
                                                                <item name="errors" xsi:type="array">
                                                                    <item name="sortOrder" xsi:type="string">0</item>
                                                                    <item name="component" xsi:type="string">Amasty_Rewards/js/view/checkout/payment/reward-messages</item>
                                                                    <item name="displayArea" xsi:type="string">messages</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
    </arguments>
</referenceBlock>

Its main part is the Javascript file Amasty_Rewards/js/view/checkout/payment/rewards and the knockout template Amasty_Rewards/checkout/payment/rewards. There is also a child component that displays messages, but we aren’t considering it now. Below is the code for the files (knockout component and knockout template) mentioned above:

define([
    'jquery',
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/quote',
    'Amasty_Rewards/js/action/add-reward',
    'Amasty_Rewards/js/action/cancel-reward'
], function ($, ko, Component, quote, setRewardPointAction, cancelRewardPointAction) {
    'use strict';
    var pointsUsed = ko.observable(null),
        pointsLeft = ko.observable(null),
        isApplied;
    isApplied = ko.observable(pointsUsed() != null);
    return Component.extend({
        defaults: {
            template: 'Amasty_Rewards/checkout/payment/rewards'
        },
        /**
         * Applied flag
         */
        isApplied: isApplied,
        pointsLeft: pointsLeft,
        /**
         *
         * @return {exports}
         */
        initialize: function() {
            this._super();
            pointsUsed(this.pointsUsed);
            pointsLeft(this.pointsLeft);
            if (pointsUsed() > 0) {
                isApplied(true);
            }
            return this;
        },
        /**
         * @return {*|Boolean}
         */
        isDisplayed: function () {
            return this.customerId;
        },
        /**
         * Coupon code application procedure
         */
        apply: function () {
            if (this.validate()) {
                pointsUsed(this.pointsUsed);
                setRewardPointAction(pointsUsed, isApplied, this.applyUrl, pointsLeft);
            }
        },
        /**
         * Cancel using coupon
         */
        cancel: function () {
            var points = pointsUsed();
            pointsUsed(0);
            cancelRewardPointAction(isApplied, this.cancelUrl);
            pointsLeft(this.pointsLeft   Number.parseFloat(points));
        },
        /**
         *
         * @return {*}
         */
        getRewardsCount: function () {
            return pointsLeft;
        },
        /**
         *
         * @return {*}
         */
        getPointsRate: function () {
            return this.pointsRate;
        },
        /**
         *
         * @return {*}
         */
        getCurrentCurrency: function () {
            return this.currentCurrencyCode;
        },
        /**
         *
         * @return {*}
         */
        getRateForCurrency: function () {
            return this.rateForCurrency;
        },
        /**
         * Coupon form validation
         *
         * @returns {Boolean}
         */
        validate: function () {
            var form = '#discount-reward-form';
            var valueValid = (this.pointsLeft - this.pointsUsed >= 0) && this.pointsUsed > 0;
            return $(form).validation() && $(form).validation('isValid') && valueValid;
        }
    });
});

As you can see, the template has an input field and two buttons: “Apply Reward” and “Cancel Reward.” Only one of them is visible at any given time, depending on whether or not the user has entered a number of points to use. When the button is clicked, the component method applies or cancel is applied accordingly. Then the apply method calls the setRewardPointAction function, which is implemented in Amasty_Rewards/js/action/add-reward. This function passes the pointsUsed and pointsLeft variables as a knockout parameter. Let’s analyze the code of this function:

/**
 * Customer store credit(balance) application
 */
define([
    'ko',
    'jquery',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/resource-url-manager',
    'Magento_Checkout/js/model/error-processor',
    'Amasty_Rewards/js/model/payment/reward-messages',
    'mage/storage',
    'mage/translate',
    'Magento_Checkout/js/action/get-payment-information',
    'Magento_Checkout/js/model/totals',
    'Magento_Checkout/js/model/full-screen-loader'
], function (ko, $, quote, urlManager, errorProcessor, messageContainer, storage, $t, getPaymentInformationAction,
             totals, fullScreenLoader
) {
    'use strict';
    return function (pointsUsed, isApplied, applyUrl, pointsLeftObs) {
        var discountAmount = quote.totals().discount_amount,
            url = applyUrl   'amreward_amount/'   encodeURIComponent(pointsUsed());
        fullScreenLoader.startLoader();
        return storage.put(
            url,
            {},
            false
        ).done(function (response) {
            var deferred;
            if (response) {
                deferred = $.Deferred();
                isApplied(true);
                totals.isLoading(true);
                getPaymentInformationAction(deferred);
                $.when(deferred).done(function () {
                    pointsUsed(Math.abs(totals.totals().discount_amount) - discountAmount);
                    pointsLeftObs(pointsLeftObs() - pointsUsed());
                    $('#amreward_amount').val(pointsUsed()).change();
                    messageContainer.addSuccessMessage({
                        'message': $t('You used '   pointsUsed()   ' point(s)')
                    });
                    fullScreenLoader.stopLoader();
                    totals.isLoading(false);
                });
            }
        }).fail(function (response) {
            fullScreenLoader.stopLoader();
            totals.isLoading(false);
            errorProcessor.process(response, messageContainer);
        });
    };
});

As you can see, the code sends a request to the server for applying points. Next, according to the response, getPaymentInformation() is called to get updates to totals and methods of payment. Then, for some reason, the discount_amount value received is recorded as the number of points used. This would be accurate if the customer received a $1 discount for 1 point; however, when the customer gets only $0.05 for 1 point (see the previously-configured discount calculation rule) this logic is incorrect. It seems that Amasty did not consider this case. It would be more logically sound to receive the number of used and remaining points from the server, but Amasty decided not to implement a separate controller to do this, which would have returned JSON. In any event, the problem must be solved.

Fixing the Checkout Bug in Amasty_Rewards 1.5.0 and 1.6.0

This fix is made up of two parts: a server part and a browser part. In Magento 2, the totals class implements the Magento\Quote\Api\Data\TotalsInterface interface and supports extension attributes. Since we don’t need to save the extension attributes on the server side and use the built-in attribute joiner, we define a new extension attribute as follows (extension_attributes.xml file).

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Quote\Api\Data\TotalsInterface">
        <attribute code="reward" type="Web4pro\Ajaxcart\Model\Reward"/>
    </extension_attributes>
</config>
Class Web4pro\Ajaxcart\Model\Reward will look as follows:

class Reward extends \Magento\Framework\DataObject
{
    /**
     * @param int $pointsLeft
     * @return $this
     */
    public function setPointsLeft($pointsLeft){
        return $this->setData('pointsLeft',$pointsLeft);
    }
    /**
     * @return int|null
     */
    public function getPointsLeft(){
        return $this->getData('pointsLeft');
    }
    /**
     * @param int $pointsUsed
     * @return $this
     */
    public function setPointsUsed($pointsUsed){
        return $this->setData('pointsUsed',$pointsUsed);
    }
    /**
     * @return int|null
     */
    public function getPointsUsed(){
        return $this->getData('pointsUsed');
    }
}

Now we add the information about the client’s points in totals. We can do this using the plugin.

<type name="Magento\Quote\Model\Cart\CartTotalRepository">
    <plugin name="provide-reward" type="Web4pro\Ajaxcart\Model\Plugin" sortOrder="20"/>
</type>
class Plugin
{
    protected $_helper;
    protected $_reward;
    protected $_objectManager;
    public function __construct(\Amasty\Rewards\Helper\Data $helper,
                                \Magentice\Ajaxcart\Model\Reward $reward,
                                \Magento\Framework\ObjectManagerInterface $objectManager){
        $this->_helper = $helper;
        $this->_reward = $reward;
        $this->_objectManager = $objectManager;
    }
    public function afterGet($model,$quoteTotals){
        $rewardsData = $this->_helper->getRewardsData();
        if(isset($rewardsData['pointsLeft'])){
            $extensionAttributes = $quoteTotals->getExtensionAttributes();
            if(!$extensionAttributes){
                $extensionAttributes = $this->_objectManager->create('Magento\Quote\Api\Data\TotalsExtension');
            }
            $this->_reward->setPointsLeft($rewardsData['pointsLeft'])->setPointsUsed($rewardsData['pointsUsed']);
            $extensionAttributes->setReward($this->_reward);
            $quoteTotals->setExtensionAttributes($extensionAttributes);
        }
        return $quoteTotals;
    }
}

As you can see, the information about points used and remaining was successfully added to the totals extension attribute and is passed to the front end. Now we have to process this information on the front end. This is where we ran into some difficulties. The knockout variables pointsUsed and pointsLeft aren’t visible outside the Amasty component, and as such, it isn’t possible to redefine the method that uses them. It’s also difficult to replace the Amasty_Rewards/js/action/add-reward function. To fix this problem, we can create event handlers for changing the knockout variables. The totals variable of the componentMagento_Checkout/js/model/totals is one of these variables.

We replace the component’s Javascript file using the layout file checkout_index_index.xml.

<referenceBlock name="checkout.root">
    <arguments>
        <argument name="jsLayout" xsi:type="array">
            <item name="components" xsi:type="array">
                <item name="checkout" xsi:type="array">
                    <item name="children" xsi:type="array">
                        <item name="steps" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="billing-step" xsi:type="array">
                                    <item name="component" xsi:type="string">uiComponent</item>
                                    <item name="children" xsi:type="array">
                                        <item name="payment" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="afterMethods" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="rewards" xsi:type="array">
                                                            <item name="component" xsi:type="string">Web4pro_Ajaxcart/js/checkout/reward</item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
    </arguments>
</referenceBlock>
The component's new file will extend the existing file and looks as follows:

define(["jquery",'mage/translate','Amasty_Rewards/js/view/checkout/payment/rewards','Magento_Checkout/js/model/totals'],
    function($,$t,Component,totals){
        return Component.extend({
            initialize:function(){
                var self = this;
                totals.totals.subscribe(function(data) {
                    self.reward = data.extension_attributes.reward;
                    self.pointsUsed = self.reward.points_used;
                    self.pointsLeft = self.reward.points_left;
                    var messages = self.regions.messages();
                    messages[0].messageContainer.successMessages.subscribe(function(data){
                        if(data.length){
                            self.pointsUsed = self.reward.points_used;
                            self.pointsLeft = self.reward.points_left;
                            if(data[0].search($t('You used'))!=-1&&data[0].search($t('You used ' self.reward.points_used))==-1){
                                messages[0].messageContainer.clear();
                                messages[0].messageContainer.addSuccessMessage({
                                    'message': $t('You used '   self.pointsUsed   ' point(s)')
                                });
                                self.initialize();
                                $('#amreward_amount').val(self.pointsUsed).change();
                            }
                        }
                    });
                });
                this._super();
                return this;
            }
    });
});
<!-- ko if: isDisplayed() -->
<div class="payment-option _collapsible opc-payment-additional rewards-add" data-bind="mageInit: {'collapsible':{'openedState': '_active'}}">
    <div class="payment-option-title field choice" data-role="title">
        <span class="action action-toggle" id="block-reward-heading" role="heading" aria-level="2">
            <!-- ko i18n: 'Apply Rewards'--><!-- /ko -->
        </span>
    </div>
    <div class="payment-option-content" data-role="content" aria-labelledby="block-reward-heading">
        <!-- ko foreach: getRegion('messages') -->
        <!-- ko template: getTemplate() --><!-- /ko -->
        <!--/ko-->
        <div class="pointsLeft" data-role="title">
            <!-- ko i18n: 'You Have '--><!-- /ko -->
            <strong data-bind="text: getRewardsCount()"></strong>
            <!-- ko i18n: ' points left '--><!-- /ko -->
        </div>
        <div class="pointsRate" data-role="title">
            <span data-bind="text: getPointsRate()"></span>
            <!-- ko i18n: ' for every'--><!-- /ko -->
            <span data-bind="text: getRateForCurrency()"></span>
            <span data-bind="text: getCurrentCurrency()"></span>
        </div>
        <form class="form form-reward" id="discount-reward-form">
            <div class="payment-option-inner">
                <input type="hidden" name="remove" id="remove-amreward" value="0" />
                <div class="field">
                    <div class="control">
                        <input class="input-text"
                               type="text"
                               id="amreward_amount"
                               name="amreward_amount"
                               data-validate="{'required-entry':true}"
                               data-bind="value: pointsUsed, attr:{placeholder: $t('Enter reward amount')} " />
                    </div>
                </div>
                <div class="actions-toolbar reward-actions">
                    <div class="primary">
                        <!-- ko ifnot: isApplied() -->
                        <button class="action action-apply" type="submit" data-bind="'value': $t('Apply Reward'), click: apply">
                            <span><!-- ko i18n: 'Apply Reward'--><!-- /ko --></span>
                        </button>
                        <!-- /ko -->
                        <!-- ko if: isApplied() -->
                        <button class="action action-cancel" type="submit" data-bind="'value': $t('Cancel Reward'), click: cancel">
                            <span><!-- ko i18n: 'Cancel Reward'--><!-- /ko --></span>
                        </button>
                        <!-- /ko -->
                    </div>
                </div>
            </div>
        </form>
    </div>
</div>
<!-- /ko -->

As you can see, we implemented an event handler for changing totals. However, that’s not enough. Immediately after executing the handler for changing totals, the callback from Amasty_Rewards/js/action/add-reward is executed, and the value of the knockout variables changes. The incorrect values are also displayed in the browser. But since the code above in Amasty_Rewards/js/action/add-reward also initializes the message for the messages child component, we can use its knockout variable to create a handler. This will also allow us to display the correct message about the client’s points. The self.initialize() call sets the required values for the knockout variables from the parent code.

We also must note that this bug in the Amasty_Rewards module wasn’t fixed in Amasty version 1.6.0, but the resolution described above only works for Magento versions 2.2.4 and later. When installing Amasty_Rewards 1.6.0 on Magento 2.2.3, the component installed threw a Javascript error on the checkout page in the browser console: “reward.js:10 Uncaught TypeError: Cannot read property ‘reward’ of undefined”

The cause of this problem is on the back end. What happened is that starting with some version, the Amasty_Rewards module depends on Amasty_Conditions, but in Amasty_Conditions the plugin was implemented in Magento\Quote\Model\Cart\CartTotalRepository the following way:

public function aroundGet(\Magento\Quote\Model\Cart\CartTotalRepository $subject, \Closure $proceed, $cartId)
{
    if (version_compare($this->productMetadata->getVersion(), '2.2.4', '>=')) {
        return $proceed($cartId);
    }
    /** @var \Magento\Quote\Model\Quote $quote */
    $quote = $this->quoteRepository->getActive($cartId);
    if ($quote->isVirtual()) {
        $addressTotalsData = $quote->getBillingAddress()->getData();
        $addressTotals = $quote->getBillingAddress()->getTotals();
    } else {
        $addressTotalsData = $quote->getShippingAddress()->getData();
        $addressTotals = $quote->getShippingAddress()->getTotals();
    }
    unset($addressTotalsData[\Magento\Framework\Api\ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]);
    /** @var \Magento\Quote\Api\Data\TotalsInterface $quoteTotals */
    $quoteTotals = $this->totalsFactory->create();
    $this->dataObjectHelper->populateWithArray(
        $quoteTotals,
        $addressTotalsData,
        \Magento\Quote\Api\Data\TotalsInterface::class
    );
    $items = [];
    foreach ($quote->getAllVisibleItems() as $index => $item) {
        $items[$index] = $this->itemConverter->modelToDataObject($item);
    }
    $calculatedTotals = $this->totalsConverter->process($addressTotals);
    $quoteTotals->setTotalSegments($calculatedTotals);
    $amount = $quoteTotals->getGrandTotal() - $quoteTotals->getTaxAmount();
    $amount = $amount > 0 ? $amount : 0;
    $quoteTotals->setCouponCode($this->couponService->get($cartId));
    $quoteTotals->setGrandTotal($amount);
    $quoteTotals->setItems($items);
    $quoteTotals->setItemsQty($quote->getItemsQty());
    $quoteTotals->setBaseCurrencyCode($quote->getBaseCurrencyCode());
    $quoteTotals->setQuoteCurrencyCode($quote->getQuoteCurrencyCode());
    if ($this->isEnterprise()) {
        $quoteTotals = $this->setExtensionAttributes($quoteTotals, $quote);
    }
    return $quoteTotals;
}

As you can see, this plugin checks the Magento version, and for versions less than 2.2.4 it implements its own code, blocking the execution of further plugins. As such, the extension attribute implemented in our plugin doesn’t even have the chance to be defined and passed to the front end. We understand why this plugin was implemented. The CartTotalRepository class had a completely different implementation until version 2.2.4, and this could create problems for a full cross-version implementation of the Amasty extension. In any event, we had to correct the above error of passing the extension attribute to the front end in case, for some reason, it isn’t possible to upgrade to newer versions of Magento. This can be done by adding the following plugin:

<type name="Magento\Quote\Model\Cart\Totals">
    <plugin name="add-gift-total" type="Web4pro\Ajaxcart\Model\Plugin" sortOrder="20"/>
</type>

public function afterSetItems($quoteTotals){
    $rewardsData = $this->_helper->getRewardsData();
    if(isset($rewardsData['pointsLeft'])&&version_compare($this->productMetadata->getVersion(), '2.2.4', '<')){
        $extensionAttributes = $quoteTotals->getExtensionAttributes();
            if(!$extensionAttributes){
                $extensionAttributes = $this->_objectManager->create('Magento\Quote\Api\Data\TotalsExtension');
            }
            $this->_reward->setPointsLeft($rewardsData['pointsLeft'])->setPointsUsed($rewardsData['pointsUsed']);
            $extensionAttributes->setReward($this->_reward);
            $quoteTotals->setExtensionAttributes($extensionAttributes);
    }
    return $quoteTotals;
}