Ultimate Magento SEO Guide: What Matters and How to Setup

Anyone who runs Magento® online store aims to reach its popularity, get more customers, and as a result, more sales. So what factors affect the success of the online store? Definitely, they are the high quality of products and services, meeting the market expectations, website performance which influences conversion rate, and SEO.

Magento SEO is similar to the optimization process of any other website. But on the other hand, Magento online shop is much more complicated. It has more pages, categories, products, and features. So what exactly matters in Magento SEO and how to set up everything correctly? Today we’ll make it clear. Meet our ultimate guide which contains certain steps on how to make Magento online store SEO-friendly and popular. Our Magento SEO technical recommendations will help you not forget all these important things.

Magento SEO Content Structure

When we say “structure”, it means the whole content logic in HTML of each web page. In Magento, there are different types of pages, such as home page, contact page, category page, product page, and more others. And by the way, the last ones are the most multiple types. Each page must have a certain content structure to be SEO-friendly. We are going to consider the main structural elements in the context of Magento.

When Google’s bot goes through the web, it scans the web pages using the “links” it can base on, like titles, meta keywords, meta description, alt-texts of images, the logically built headers, and URLs. It aims to provide the users with the most relevant information, and these elements together help it learn the level of relevancy. It goes through the sitemap and stops on the pages that are marked like robot.txt (the pages which are not enabled to index). So this structure must be logically clear and give the certain sense of the content you post. If it is, each web page gets indexed and becomes visible for web users.

You can carry out and control your SEO using Google Search Console.

Magento SEO Titles

The title is the heading of your web page. It is the first visible element in Google search results colored in blue. It’s quite important to create a unique title for each Magento page (category, product, home and others.). And the main: a title should contain your focus keywords.

It looks like: Keyword1 Keyword 2…- Your Company Name

Setup titles in Magento: System -> Configuration -> Design-> HTML Head-> Default Title.

Example: In case the category page for business laptops, use a title like:

Business Laptops – Computer Shop. (“Computer Shop” is imagined brand name).

Magento SEO Metadata

Metadata is one of the most important types of information for search engines. It contains keywords, meta description. We’ll not stop on how to make a keyword research or write a description. You can read more about SEO-elements if you need.

You should just know that it’s necessary to pick the focus keywords for each page.

Setup keywords in Magento: System -> Configuration -> Design-> HTML Head-> Default Keywords.

Setup description in Magento: System -> Configuration -> Design-> HTML Head-> Default Description.

Magento SEO URLs

Here we’ll look at the common issues related to URLs you should keep in mind.

Human- and SEO-Friendly URLs

Don’t forget to include the keywords and relevant information to your URLs, where your pages sit. Each URL should be readable and tell the relevant information. Let’s take a look at some product page. Here is an example of good URL for the certain product: yourdomainname.com/puma-sneakers-for-run-white

Avoiding Duplicate Content

The duplication of content happens in several cases. We’ll consider them below

Domain Name Issue

The first one happens when your shop is delivered by the both types of domain name: with www and without www. It looks something like this:

http://www.websitename.com

http://website.com

If your Magento store is loaded with any of this types, congratulations, you have the duplicate content. But you can fix this and set up your domain name in Magento: System –> Configuration –> Web –> Unsecure –> Base URL.

Enter and save your domain name. Then make sure that your website doesn’t generate duplicate content and is available only by the domain name you’ve chosen.

Enable this option (choose „Yes“) using the following way: System –> Configuration –> Web –> Search Engines Optimization –> Use Web Server Rewrites.

Rel=Canonical Turning On

Turn on rel=canonical for the categories and products. It’s pretty helpful in reducing the number of duplicate content.

Setup rel=canonical: System –> Configuration –> Catalog –> Search Engine Optimisation -> „Use Canonical Link Meta Tag For Products“ and „Use Canonical Link Meta Tag For Categories“.

Set Up Redirects

Here the function Create Permanent Redirect for old URLs if Url key change will be helpful when you migrate your website or apply the certain changes to your URLs. Choose “Yes” for this function.

How to turn it on: System –> Configuration –> Catalog –> Create Permanent Redirect for old URLs if Url key changed

Don’t Set Up Categories Path for Product URLs

It’s better to turn off this function and choose “No” for it, in order not to make Google confused when you change the category name and all products set on this path can vanish from the index.

How to turn it off: System–>Configuration–>Catalog–>Use Categories Path for Product URLs

Also, there are some useful extensions that can help you on your way to great SEO.

Magento SEO Headings

Headings divide the page content into parts and name them. In HTML, headings follow locally one by one like H1, H2, H3, H4 tags and so on so forth. You should follow this logic and not miss the heading level when creating your content. H3 should follow H2. You can’t miss H4 and place H5 after H3. Also, include your keywords here.

Magento SEO Image Attributes

Everyone who carries out SEO should know that the search engines are not able to see what in the picture is. The only way to help them is to add the image attributes: image title and alt-text.

If talk about Magento with its numerous product images, this moment is quite critical here. Alt-text and image title should also include the keywords.

Magento SEO Links

Such important content elements, as links and anchors, should also contain the keywords. Internal site links for SEO are under your control in Magento, but also the system generates the category-related links automatically. Each page should be reachable through the website. For these purposes, webmasters should create a sitemap.

Sitemap.xml and Robot.txt in Magento SEO

These elements are devoted to helping the search engines follow the right way through the website. Sitemap.xml is a listing data of your store pages and links.

Where to find the Sitemap: Admin Panel -> System -> Configuration -> Google Sitemap. Then Enable it in Catalogue -> Sitemap.

A tip: it’s better to do this process after your website release. Before, don’t turn on “index, follow”, and use subdomain with an access password in the stage of development.

Setup Index Follow: System –> Configuration –> Design –> HTML Head –>  Default robots “INDEX, FOLLOW”. – Turn on after release.

Check your sitemap file. Only then add it to Google Webmasters Tools.

If you run a multiple Magento online store, for example, Chinese and German, separate your sitemaps and call them like sitemap-ch.xml and sitemap-ge.xml.

Robot.txt is the file that contains the information about the website URLs Google’s robot should not crawl through. But note that it could keep the pages in the index (but not show the other elements, like title and description for these pages). Creating this file is a process that needs to be well-thought-out and performed. You can read more about it in Inchoo guide.

Content Quality in Magento SEO

The content quality definitely matters in SEO. What does it mean?

First of all, it’s the relevance of the content. Google’s most task is to provide the users with the most relevant information.

If you pick the keywords, they should make sense in the website texts, such as category and product descriptions, product cards, landing pages, and so on so forth.

The next thing is that the content should be unique, interesting, and attractive. Never try to use someone’s content or copy it from the other website. Such actions are forbidden by Google. Don’t only advertise. Give something useful.

Never use the black methods like hidden, bought, and broken links, spam, links in a bad neighborhood, overlinking. They will not boost the brand but destroy the reputation.

And remember the following: the more traffic generates your online store, the more conversion it gets, and the more popular is your brand, the higher position in the search results it gets.

Measurement of Magento SEO Results

It is not a step. It’s a continuous process that we have to for performing the best SEO practices. You should know how to measure results from the SEO campaigns. And here Google’s tools are very useful. The best helpers for watching your SEO are Google Search Console, and the most powerful platform for deep analysis of traffic sources is Google Analytics. Try to make friends with Google, and your Magento SEO will be much better.

Full Page Cache Magento Extensions for the High Website Performance

Last time we discussed how to optimize Magento® performance. It’s a pretty important process for everyone who owns the website, especially eCommerce online store. Page loading time affects the conversion rate dramatically. It’s an interesting fact that if the page is loaded more than 3 seconds the visitor goes away. In the worst scenario, a website is hopeless at selling products, and you lose potential customers. But everyone can avoid it and turn this task into benefits. By the way, the search engines consider page loading time. And the websites that load faster get the higher positions in search results.

If your website is based on Magento, you are familiarized with its power. And that means your online store resists a high load of visitors on a daily basis. Is it ready for such a huge pressure, for example, when you run season sales at your store, and the number of visitors goes sky high? You can check your Magento based website for the most important parameters using our checklist. If you find something you can improve, just do it.

Checklist on How to Speed up Magento

  • Migrate to the Latest Version
  • Perform Image Optimization
  • Enable Flat Catalog
  • Concatenate (merge) CSS and Javascript Files
  • Enable Magento Compilation
  • Utilize Caching
  • Choose Fast Web Hosting
  • Clean Up Magento Database & Logs
  • Download Fewer Extensions & Modules
  • Perform MySQL Configuration
  • Update Indexes
  • Use PHP Accelerators

Now you can move on to the list of Magento Extensions to improve your website performance with full page caching.

Classic Scheme of Full Page Cache Magento Extensions

Imagine the user. He is opening the page of some Magento online store the first time. What happens then? Here are where Magento starts its work. It creates and delivers the required web page to a user and saves its copy in cache. This copy is stored there, and next time when the visitor goes to this page, he gets this copy. So, you see that the way is shortened. It goes from the user to cache. This process increases page loading speed in many times.

Top 3 Useful Full Page Cache Magento Extensions

As we work on Magento on a daily basis, and Magento development is our main expertise, we face different tasks related to Magento performance optimization. We can give you several recommendations on the choice of the useful full page cache Magento extensions based on our technical experience. You can try them and speed up your Magento based website. Before, you can view our checklist to learn what items you can improve right now.

Full Page Cache by Mirasvit

This solution works following the classic scheme of page caching and helps to increase the speed of Magento based website. It returns the web pages copies from the cache immediately. It allows you to reduce page loading time in 6-25 times comparing to default Magento. And this is an advantage of the extension.

Features:

  • full page cache for all website pages;
  • excludes required pages from the cache;
  • supports themes for mobile devices;
  • you can use different types of cache;
  • built-in crawler.

It also includes the detailed crawling statistic that helps you to keep cache up-to-date.

Where to get and learn more: https://mirasvit.com/

Full Page Cache by Amasty

Full Page Cache by Amasty also helps you to decrease server load and speed up your website. The distinctive feature of this extension is the support of dynamic blocks, called “holes” punch. So you can choose whether you want to use dynamic block caching or not.

Features:

  • full page cache for web pages;
  • dynamic blocks caching (enable/disable);
  • supports themes for mobile devices;
  • clearing cache for the whole website and its specific pages individually;
  • caching of Ajax requests;
  • uses native Magento functionality and compatible with default Magento;
  • Includes full page cache crawler;
  • supports LSS websites and compatible with content delivery networks.

Where to get and learn more: amasty.com

Lesti_Fpc Extension

Lesti_Fpc extension is an internal solution for Magento. As external extension work in front of Magento, they reload dynamic content with Ajax. Yes, they are fast and efficient. But if you need more flexibility, Lesti_Fpc extension is one of the best solutions for you. It is an internal extension. It requires a bit more time for caching but could be compatible mostly with any Magento theme without much customization and development processes.

And a great plus that it’s absolutely free.

Features:

  • full page caching;
  • works with events;
  • replaces dynamic fields;
  • can exclude a category and a product;
  • 24-hour cache lifetime;
  • compatible with 1.5 – 1.9 Magento CE versions.

Where to get and read the instruction: github.com

Where to learn more: gordonlesti.com

How to Optimize Magento Performance Without Coding

If you have a website based on Magento®, sooner or later, you face the question of how to optimize Magento performance. We have already discussed how page loading speed influences conversion. So it’s time to make it clear and finally get the working steps on how to speed up Magento based website. Let’s start.

Magento Performance and Website Speed Test

Although the process of Magento performance optimization is quite difficult and depends on different parameters, there are several basic things that can help you decrease your website loading time. You can speed up Magento by yourself using our Magento optimization techniques. Let’s start from scratch and check whether your website is optimized or not. Then run the Google speed test and learn how fast your website is.

If you see that your page loading time needs to be reduced, we can move on.

Magento Performance Tuning

The process of performance optimization for any CMS and website is quite similar: we should keep up with the times and reduce page loading time by reducing the weight of content and code. We need to shorten the way of servers request. How to do it? Let’s do the following:

Use the Latest Version of Magento for Better Performance

It’s quite important to use the latest version of CMS and modules because the bugs are fixed, and performance is improved in updates. Also, it will help you to keep your Magento based website protected.

Optimise Images and Reduce Their Weight

Surprisingly, but more than 50% of page loading time is taken by images loading. Here we move to reduce the weight of content. So, where are the images on your website? For sure, you’ll find them in Products and Categories. If you just decrease their size and use the compression, it will drastically affect the page loading time. You can do it with help of different extensions, but also the simple way is to use “Save for Web” in Adobe PhotoShop (you’ll find this function in header menu, “File” tab).

Flatten Your Categories and Products

Imagine that your website has a huge number of categories and products. And all of them have their own attributes. These attributes are stored in separate databases. So while reaching some product from some category, the way of the request is too long. That’s why if you enable flat categories and products, the attributes will be in one table. Therefore, you shorten this way. Magento website speed will be increased.

How to enable flat categories and products:

  • go to Magento admin panel;
  • choose System in top menu;
  • then choose Catalog.
  • Catalog Frontend and set Yes for “Use flat Categories” and “Use flat products”.

Combine CSS and JS Files into One File

This will also have a positive impact on a website performance. It’s useful when it comes to the time of server request. If you merge CSS and JS files, you’ll shorten the way of request and optimize your code.

How to merge CSS and JS file:

  • go to Magento admin panel;
  • choose System in top menu;
  • then choose Configuration, Advanced.
  • go to Developer, CSS settings, and JavaScript settings;
  • there you should set Yes for “Merge Javascript” Files and “Merge CSS” Files.

We are on the right way. We have already done the most important steps. Let’s move to the next one.

Enable Compilation and Activate the Scripts

This step helps to activate some important scripts in Magento core, and the website becomes faster.

How to enable compilation:

  • go to Magento admin panel;
  • choose System in top menu;
  • then choose Tools, Compilation, Enable.

Enable Caching

Cache is quite a powerful element when it comes to website performance optimization. It helps to shorten the way of server request by storing different data that was loaded before. There are different types of caching in Magento. We offer you to enable all of them and look at the result.

How to enable caching:

  • go to Magento admin panel;
  • choose System in top menu;
  • then choose Cache Management, select all items and choose Enable.

Magento admin panel

Also, don’t forget to turn on the page cache. It’s a pretty useful and powerful way to increase your Magento website speed. You can use special extensions for these purposes, for example:

Zoom Full-Page Cache, LiteMage Cache, Product & Category Page Cache

Choose the Fastest Web Hosting

It’s an important step which you should do right at the beginning of your Magento performance optimization. It is a base for your website, that’s why it should be fast and reliable enough in order to resist the high load and perform the best efficiency. It’s better to prefer not cheap but well known hosting providers with a good reputation and reliable technical support.

How to Install Magento 2 on Windows 10 Using Local OpenServer

Install Magento® 2 on Windows 10… Sounds good, right? Today we’ll run through the process on how to make it work using OpenServer.

Magento Installation Steps

Anytime you are going to deal with Magento 2, you’ll find the list of specific technical requirements, such as the following ones:

Magento 2 technical requirements

It’s just a part of them. The first requirement is operating system requirements: Linux x86-64. But should we do if our task is working with Magento 2 on Windows 10? OpenServer is a right solution in this case. The matter is that symlinks don’t work on Windows 10 for Magento 2.

However, OpenServer and the correct install of Node.js are helpful in solving this problem. It’s our way of how to work with Magento 2 and Node.js. Enough words – follow our Magento install instructions guide.

Setting the local OpenServer in order to Install Magento 2 on Windows 10

  • Install Node.js if it’s not set. You can find the Node.js official installer here. It is set to Program Filesnodejs by default.
  • Run OpenServer with admin access. The symlinks will work only in case you use admin access.
  • Open settings. On the ‘Server’ tab and “Setting the use of Path variable” field, we should set ‘Own Path userdata/config/path.txt’.
  • Following the path: Your_drive:OpenServeruserdataconfig (Your_Drive is a drive’s letter where OpenServer is set), create a path.txt file with the next content:

C:Program Filesnodejs

C:UsersyouAppDataRoamingnpm

The second row here is a path of setting grunt, gulp, etc. If you have the other files location, don’t forget to correct the paths.

  • Rerun the server.
  • Run OpenServer console.
  • Check whether node.js works. Use node v command.
  • Install Grunt: npm install -g grunt-cl
  • Check whether Grunt works. Use grunt v command.

Now Node.js and Grunt are set on your OpenServer globally.  You can set any other packages, such as Bower, Gulp, etc using our approach.

The main thing here is to write path.txt. file paths correctly. That’s all!

Magento Import Tax Rates. The Full Guide

Magento® 2 provides the calculation of the tax amount when creating an order. Magento firstly was developed for the USA, because sales tax rates can be different in different locations (county/city). The default Magento 2 functionality allows setting the tax rate by the following parameters: country, region (state/province), postcode. The administrator can set the tax rates in Stores -> Tax Zones, and Rates menu on admin panel. But as the USA has a lot of locations with different postcodes, it could be a bit challenging to output all necessary tax rates pegged to the postcode manually.

Now we’ll consider the way of how to do this.

How In Magento Set Tax Rate Programmatically

Let’s imagine the CSV-file of Magento tax class for shipping rates (more than 1000 rows) for Northern Carolina state that has the following format:

State,ZipCode,TaxRegionName,StateRate,EstimatedCombinedRate,EstimatedCountyRate,EstimatedCityRate,EstimatedSpecialRate,RiskLevel

We’ll pay attention to a ZipCode field which contains the location postcode and Estimated Combined Rate presented in the decimal format.

$api_url = "….";//link to Magento 2 Store
$ch = curl_init();
curl_setopt($ch,CURLOPT_POST,true);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode(array('username'=>'admin','password'=>'...')));
curl_setopt($ch,CURLOPT_HTTPHEADER,array("Content-Type:application/json"));
curl_setopt($ch,CURLOPT_URL,$api_url.'rest/V1/integration/admin/token/');
$res = curl_exec($ch);
$result = json_decode($res,true);
if(isset($result['message'])){
  echo $result['message'];
  exit(0);
}
$token = $result;//Getting the connection token 
$file = fopen('TAXRATES_ZIP5_NC201701.csv','rt');
$header = fgetcsv($file);
while($row = fgetcsv($file)){
   $taxArray = array(
     'tax_country_id'=>'US',
     'tax_region_id'=>44,
     'tax_postcode'=>$row[1],
     'code'=>'US-NC-'.$row[1],
     'rate'=>$row[4]*100
   );
    curl_setopt($ch,CURLOPT_POST,true);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode(array('taxRate'=>$taxArray)));
curl_setopt($ch,CURLOPT_HTTPHEADER,array("Content-type: application/json","Authorization: Bearer ".$token));
curl_setopt($ch,CURLOPT_URL,$api_url.'rest/V1/taxRates');
$res = curl_exec($ch);
echo $res."<br/>";   
}
fclose($file);

This example describes the tax rate to percent value because the tax rates are set exactly in this format in Magento 2. As we needed to place the configuration of the tax rates only for Northern Carolina, it was not difficult to find the identifier of this state in Magento based online store. We created an entry in tax_calculation_rate table for each location by requesting _/V1/taxRates() API method and transmitting it the array in JSON format as a parameter.

If the file provided the data for locations in different states, we would better get the array of states using_/V1/directory/countries/US() API method which would return the array with information about the USA from Magento 2. This array would include state id – state code binding. First, we would get the state code, and then we would place the state id in the request for the appropriate state code from State column.

Magento 2: How to Add a Custom Attribute to The Customer’s Address

When developing and modifying an online store, it often comes up the task to add attributes to the entities. Let’s suppose; there is an online store based on Magento® 2 which retails the products not only for customers but also for some businesses (corporate clients). The process requires that the on the client must indicate whether the shipping address is the Residence or the Business on the order page. The data entered by the customer must get to the order address. In case the customer is authorized, these data must also get to the client’s address book for later use.

When having this task, you may face two difficulties:

  • correct implementation of the entered data storage (backend);
  • displaying the attribute input (select) field on the checkout page (frontend).

We’ll consider these tasks below.

Magento 2: Adding a New Field In Address Form

In Magento 2, the checkout page is formed using the knockout javascript component. This component gets the fields which the user can output from the configuration. The configuration is transferred from the server to the browser in JSON format. The output of system fields is implemented in core, but the output of an additional field must be implemented with the extension. You can look through the example of adding the field here.

Magento 2: Implementation of The Entered Data Storage

Now we’ll pay close attention to the backend, and, more exactly, implementation of the entered data storage. The most required for the way of data storage is the easy access to data and the ability to copy them.

In Magento 2, the customer’s address from the address book is EAV. Therefore, it can have additional attributes which are stored in tables created by Magento developers. You can easily add the attribute using the installation script of InstallData module, for example, using the following method:

$eavSetup = $this->_eavSetupFactory->create(['setup' => $setup]);
        $eavSetup->addAttribute('customer_address', 'type', array(
            'type' => 'int',
            'input' => 'select',
            'label' => 'Address Type',
            'source'=>'Web4pro\Ajaxcart\Model\Address\Type',
            'global' => 1,
            'visible' => 1,
            'required' => 1,
            'user_defined' => 1,
            'system'=>0,
            'group'=>'General',
            'visible_on_front' => 1,
        ));
        $eavSetup->getEavConfig()->getAttribute('customer_address','type')
               ->setUsedInForms(array('adminhtml_customer_address','customer_address_edit','customer_register_address'))
               ->save();

An additional attribute will be created, and its values will be stored in customer_address_entity_int table because the attribute is not static. The static attributes are stored in customer_address_entity table, and all system attributes of this entity are static.

When the user adds the product to the shopping cart, goes to the checkout page, and enters their address, the address is saved not to the address book (there could be no address book if the user is not authorized), but to the shopping cart entity. Physically, it’s quote_address table, and we can work with it with \Magento\Quote\Model\Quote\Address. The user can enter two addresses overall: a payment address and a shipping address. When creating the order, addresses from the shopping cart are copied to the order (sales_order_address table and \Magento\Sales\Model\Order\Address). These models are not EAVs. Mainly, Magento 2 implementation is similar to Magento 1 one, but at the same time, there are several important differences.

Magento 2: Working with Address Attributes

In Magento 2 and Magento 1, the task to copy address fields is implemented with a fieldset configuration. In Magento 2, etc/fieldset.xml file in module configuration is responsible for this. In Magento 1, we would add the necessary fields to quote_address and sales_order_address tables, write the attributes to the fieldset configuration, and the core code would have copied the necessary values. But in Magento 2, it doesn’t work. And this is why.

The data copying is implemented in Magento\Framework\Api\DataObjectHelper class in public populateWithArray() method which calls protected() method with practically the same parameters. Look at its implementation below:

protected function _setDataValues($dataObject, array $data, $interfaceName)
    {
        $dataObjectMethods = get_class_methods(get_class($dataObject));
        foreach ($data as $key => $value) {
            /* First, verify is there any setter for the key on the Service Data Object */
            $camelCaseKey = \Magento\Framework\Api\SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key);
            $possibleMethods = [
                'set' . $camelCaseKey,
                'setIs' . $camelCaseKey,
            ];
            if ($key === CustomAttributesDataInterface::CUSTOM_ATTRIBUTES
                && ($dataObject instanceof ExtensibleDataInterface)
                && is_array($data[$key])
                && !empty($data[$key])
            ) {
                foreach ($data[$key] as $customAttribute) {
                    $dataObject->setCustomAttribute(
                        $customAttribute[AttributeInterface::ATTRIBUTE_CODE],
                        $customAttribute[AttributeInterface::VALUE]
                    );
                }
            } elseif ($methodNames = array_intersect($possibleMethods, $dataObjectMethods)) {
                $methodName = array_values($methodNames)[0];
                if (!is_array($value)) {
                    if ($methodName === 'setExtensionAttributes' && $value === null) {
                        // Cannot pass a null value to a method with a typed parameter
                    } else {
                        $dataObject->$methodName($value);
                    }
                } else {
                    $getterMethodName = 'get' . $camelCaseKey;
                    $this->setComplexValue($dataObject, $getterMethodName, $methodName, $value, $interfaceName);
                }
            } elseif ($dataObject instanceof CustomAttributesDataInterface) {
                $dataObject->setCustomAttribute($key, $value);
            }
        }

        return $this;
    }

As you can see, this method searches through the incoming array of data and writes them into $dataObject. Only determined methods in the object class are used for writing. If the writing method isn’t found and the entity implements CustomAttributesDataInterface, it will try to write a value with setCustomAttribute.

\Magento\Quote\Model\Quote\Address and \Magento\Sales\Model\Order\Address models implement this interface, but we will not be able to use it for the following reasons. This method checks the presence of enabled attributes which _getCustomAttributesCodes() method returns only when our key matches the attribute code. _getCustomAttributesCodes() method is implemented in Magento\Framework\Model\AbstractExtensibleModel class which is a parent for \Magento\Quote\Model\Quote\Address and \Magento\Sales\Model\Order\Address. But this method returns the empty array and it is protected, so we can’t add our own attribute codes using the plugin. We can only redefine the class with dependency-injection which is an undesirable way of customization.

That’s why we’ll use another way. All classes which are inherited from Magento\Framework\Model\AbstractExtensibleModel also support extension_attributes. The attribute of this kind should be declared in etc/extension_attributes.xml file of the module. The attribute could have a simple scalar type (int, string) as well, as a type of some class. A module developer can implement the saving of this attribute, but Magento also has the standard built-in tools of attributes data uploading. We can’t save this attribute into the table where the main entity which it’s added to is placed. It should be a separate table, and we can put the link to this table in the description. In order to do this task, we’ll show the content of etc/extension attributes.xml file below.

<?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\AddressInterface">
        <attribute code="type" type="int">
            <join reference_table="web4pro_quote_address" join_on_field="address_id" reference_field="address_id">
                <field column="type">type</field>
            </join>
        </attribute>
    </extension_attributes>
    <extension_attributes for="Magento\Sales\Api\Data\OrderAddressInterface">
        <attribute code="type" type="int">
            <join reference_table="web4pro_order_address" join_on_field="entity_id" reference_field="entity_id">
                <field column="type">type</field>
            </join>
        </attribute>
    </extension_attributes>
    <extension_attributes for="Magento\Customer\Api\Data\AddressInterface">
        <attribute code="type" type="int"/>
    </extension_attributes>
</config>

The extension attribute is defined not for a class, but for the interface. It’s necessary to get the code and the type defined for the attribute. You can also describe the table where it’s stored as well, as a rule of join request to this table. In this case, the attribute address type will be saved in an ordinary flat table which is connected with shopping cart address table via address_id key, and entity_id key – with the order address table. Therefore, it’s necessary to create each table in Setup/InstallSchema.php class and implement the model and resource model for each table. We can write data to the table using the save_after event processors for the order and the shopping cart.

<event name="sales_quote_address_save_after">
        <observer name="ajaxcart" instance="Web4pro\Ajaxcart\Observer\Address" shared="false" />
    </event>
    <event name="sales_order_address_save_after">
        <observer name="ajaxcart" instance="Web4pro\Ajaxcart\Observer\Order\Address" shared="false" />
    </event>
namespace Web4pro\Ajaxcart\Observer;

class Address implements \Magento\Framework\Event\ObserverInterface {

    protected $_objectManager;

    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManagerInterface){
        $this->_objectManager = $objectManagerInterface;
    }

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

        if($address = $observer->getEvent()->getQuoteAddress()){
           if($attributes = $address->getExtensionAttributes()){
               $customAddress = $this->_objectManager->create('\Web4pro\Ajaxcart\Model\Quote\Address');
               $customAddress->setType($attributes->getType())->setAddressId($address->getId())->save();
           }
        }
    }
} 

namespace Web4pro\Ajaxcart\Observer\Order;

class Address implements \Magento\Framework\Event\ObserverInterface {

    protected $_objectManager;

    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManagerInterface){
        $this->_objectManager = $objectManagerInterface;
    }

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

        if($address = $observer->getEvent()->getAddress()){
            if($attributes = $address->getExtensionAttributes()){
                $customAddress = $this->_objectManager->create('\Web4pro\Ajaxcart\Model\Order\Address');
                $customAddress->setType($attributes->getType())->setId($address->getId())->save();
            }
        }
    }
}

As there are usually two addresses in the shopping cart and the order: shipping and delivery, both addresses are loaded with the collection. It’s pretty easy to add the attributes to the collection. You can do it using the load_before processor of the collection event via\Magento\Framework\Api\ExtensionAttribute\JoinProcessor object.  Let’s look at its implementation:

<event name="sales_quote_address_collection_load_before">
        <observer name="ajaxcart" instance="Web4pro\Ajaxcart\Observer\AddressCollectionLoad" shared="false" />
    </event>
    <event name="sales_order_address_collection_load_before">
        <observer name="ajaxcart" instance="Web4pro\Ajaxcart\Observer\Order\AddressCollectionLoad" shared="false" />
    </event>
namespace Web4pro\Ajaxcart\Observer;

class AddressCollectionLoad implements \Magento\Framework\Event\ObserverInterface {

    protected $_joinProcessor;

    public function __construct(\Magento\Framework\Api\ExtensionAttribute\JoinProcessor $joinProcessor){
        $this->_joinProcessor = $joinProcessor;
    }

    public function execute(\Magento\Framework\Event\Observer $observer){
        if($collection = $observer->getEvent()->getQuoteAddressCollection()){
            $this->_joinProcessor->process($collection);
        }
    }
} 

namespace Web4pro\Ajaxcart\Observer\Order;

class AddressCollectionLoad implements \Magento\Framework\Event\ObserverInterface {

    protected $_joinProcessor;

    public function __construct(\Magento\Framework\Api\ExtensionAttribute\JoinProcessor $joinProcessor){
        $this->_joinProcessor = $joinProcessor;
    }

    public function execute(\Magento\Framework\Event\Observer $observer){
        if($collection = $observer->getEvent()->getOrderAddressCollection()){
            $this->_joinProcessor->process($collection);
        }
    }
}

As a result, we can get the extension attributes after loading the collection  using _getExtensionAttributes() method which returns the entity that implements \Magento\Quote\Api\Data\AddressExtensionInterface for the shopping cart address and \Magento\Sales\Api\Data\OrderAddressExtensionInterface for the order address. The interface and the class which implements it are dynamic. And they are formed right on the go in var/generation directory on the base of the description in etc/extension_attributes.xml files of different modules for this type.

But there are some pitfalls in this implementation. Say, you successfully outputted the attribute to the checkout page (we’ll show how to do it below) and saved, for example, the shipping address. But in case we reload the checkout page or go to the shopping cart, we could get the empty checkout page without any content or the error message in the main log. Here is how it looks like:

Recoverable Error: Argument 1 passed to Magento\Quote\Model\Cart\Totals::setExtensionAttributes() must be an instance of Magento\Quote\Api\Data\TotalsExtensionInterface, instance of Magento\Quote\Api\Data\AddressExtension given, called in /var/www/brandrpm/vendor/magento/framework/Api/DataObjectHelper.php on line 127 and defined in /var/www/brandrpm/vendor/magento/module-quote/Model/Cart/Totals.php on line 592 [] []

We can find the reason for this error in the Magento\Quote\Model\Cart\CartTotalRepository class. This class implements Totals values load for display. It’s implemented the following way:

public function get($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();
        }

        /** @var \Magento\Quote\Api\Data\TotalsInterface $quoteTotals */
        $quoteTotals = $this->totalsFactory->create();
        $this->dataObjectHelper->populateWithArray(
            $quoteTotals,
            $addressTotalsData,
            '\Magento\Quote\Api\Data\TotalsInterface'
        );
        $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());
        return $quoteTotals;
    }

As you can see, this method just extracts the _data array values from the address entity and puts them to the entity which implements \Magento\Quote\Api\Data\TotalsInterface. It’s done this way because Totals values are physically stored in the address table and uploaded to the address entity. And the extension entities attributes of various classes can implement different and even incompatible interfaces. By the way, the same would happen when copying extension attributes using etc/fieldset.xml. In our case the file will look the following way:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd">
    <scope id="global">
        <fieldset id="customer_address">
            <field name="type">
                <aspect name="to_quote_address" />
            </field>
        </fieldset>
        <fieldset id="sales_convert_quote_address">
            <field name="extension_attributes">
                <aspect name="to_customer_address" />
                <aspect name="to_order_address" />
            </field>
        </fieldset>
        <fieldset id="sales_convert_order_address">
            <field name="extension_attributes">
                 <aspect name="to_quote_address" />
            </field>
        </fieldset>
    </scope>
</config>

We can sort out the problem of type incompatibility using the plugin for populateWithArray() method.

<type name="Magento\Framework\Api\DataObjectHelper">
        <plugin name="move-extension-attributes" type="Web4pro\Ajaxcart\Model\Plugin" sortOrder="20"/>
    </type>
public function beforePopulateWithArray($helper,$dataObject, array $data, $interfaceName){
        switch($interfaceName){
            case '\Magento\Sales\Api\Data\OrderAddressInterface':
                if($data['extension_attributes'] instanceof \Magento\Quote\Api\Data\AddressExtensionInterface){
                    $data['extension_attributes'] = $data['extension_attributes']->__toArray();
                }
            break;
            case '\Magento\Customer\Api\Data\AddressInterface':
                if($data['extension_attributes'] instanceof \Magento\Quote\Api\Data\AddressExtensionInterface){
                    $data['extension_attributes'] = $data['extension_attributes']->__toArray();
                    if(isset($data['extension_attributes']['type'])){
                        $data['type'] = $data['extension_attributes']['type'];
                    }
                }
                break;
            case '\Magento\Quote\Api\Data\TotalsInterface':
                unset($data['extension_attributes']);
                break;
        }
        return array($dataObject,$data,$interfaceName);
    }

As the interface name of the source object is transmitted to _populateWithArray() method, we can check it and modify the data. We can get away extension attributes at all, as it’s done for \Magento\Quote\Api\Data\TotalsInterface in our case. But if we need any extension attributes for this interface, it’s better to use __toArray() method which any extension attribute entity has and which returns the array key – the extensions attribute value. If transmit this array to _populateWithArray() method, it will automatically write it to the extensions attribute value and transform it into the type of the relevant interface.

The question is: “Why Magento 2 developers haven’t included the automatic converting of extension attribute values to the array for the comfort transmitting between the entities?” – We can only suppose all extension attribute classes implement __toArray() method, which is implemented in Magento\Framework\Api\AbstractSimpleObject parent class. For \Magento\Quote\Api\Data\AddressExtensionInterface, we also saved the attribute value right to the $data array, because in this case this value will be written with _setCustomAttribute() method, and the entity of this interface supports all created EVAs.

You can look at the frontend implementation here. But we must admit that Mixin Javascript is implemented the next way:

define([
    'jquery',
    'mage/utils/wrapper',
    'Magento_Checkout/js/model/quote'
], function ($, wrapper, quote) {
    'use strict';

    return function (setShippingInformationAction) {

        return wrapper.wrap(setShippingInformationAction, function (originalAction) {
            var shippingAddress = quote.shippingAddress();
            if (shippingAddress['extension_attributes'] === undefined) {
                shippingAddress['extension_attributes'] = {};
            }
            var tp = shippingAddress.customAttributes['type'];
            if(typeof tp=='object'){
                tp = tp.value;
            }
            shippingAddress['extension_attributes']['type'] = tp;
            // pass execution to original action ('Magento_Checkout/js/action/set-shipping-information')
            return originalAction();
        });
    };
});

ShippingAddress.customAttributes[‘type’] value checking also should be implemented. Because if the user who places the order has already given the address, and this address is already saved in the address book, the result is the entity in this variable. We must transmit only the value to the server. This variable will have the scalar type for a new address.

The value output on the order view page on admin panel is implemented with a simple plugin:

<type name="Magento\Sales\Block\Adminhtml\Order\View\Info">
        <plugin name="render-type" type="Web4pro\Ajaxcart\Model\Plugin" sortOrder="20"/>
    </type>
public function beforeGetFormattedAddress($block,$address){
        if($attributes = $address->getExtensionAttributes()){
            $address->setType($attributes->getType());
        }
        return array($address);
    }

After all, we must only add the {{var type}} variable to the address format in Magento configuration, and as a result, this field will be outputted.

Creating Magento Price Rules Using Amasty_RulesPro

Do you often work with pricing rules in Magento® 2? For example, you need to implement the complex rules when a customer gets the discount which depends on the customer’s previous order total amount.

While everything is clear enough with the rules in Magento, there is no default functionality that can help set up Magento custom price rules for the customer entity. Say, in Magento 2, you can set the rules which depend on the customer’s’ gender, a country which the customer is from, and the other parameters. But if we talk about complex pricing rules, we should use additional modules for Magento 2 to set them.

However, even in this case, not every module can cope with this task by default. Today we’ll show how we solved this problem, and you’ll learn the useful method of how to implement pricing rules in Magento 2 using the Amasty_RulesPro module.

Task: Create Pricing Rules in Magento 2

Our task was to apply the pricing rule to all clients which belong to one company. A company is a custom entity which the client connected with by the company_id custom attribute. For these purposes, we used Amasty_RulesPro module.

Amasty_RulesPro module for Magento 2 extends Magento SalesRule functionality, in particular, it allows you to use any customer entity’s attributes. However, you can face the problem with the attributes that are not static. Let’s consider the reason and solution for this problem.

The Reason for Non-Static Attributes Error in Amasty_RulesPro

Condition validation is implemented invalidate() method, of Amasty\RulesPro\Model\Rule\Condition\customer. You can implement this method as follows:

public function validate(\Magento\Framework\Model\AbstractModel $model)
    {
        $customer = $model;
        if (!$customer instanceof \Magento\Customer\Model\Customer) {
            $customer = $model->getQuote()->getCustomer();
            $attr = $this->getAttribute();

            $allAttr = $customer->__toArray();

            if ($attr == 'membership_days') {
                //$customer->setData($attr, $this->getMembership($customer->getCreatedAt()));
                $allAttr[$attr] = $this->getMembership($customer->getCreatedAt());
            }
            if ($attr != 'entity_id' && !array_key_exists($attr, $allAttr)){
                $address = $model->getQuote()->getBillingAddress();
                $allAttr[$attr] = $address->getData($attr);
            }
            $customer = $this->_objectManager->create('Magento\Customer\Model\Customer')->setData($allAttr);
        }
        return parent::validate($customer);
    }

In case the transferred model is not a \Magento\Customer\Model\Customer subclassing, this entity is got from the Quote object. But in Magento 2, getCustomer() method of \Magento\Quote\Model\Quote class returns the entity of \Magento\Customer\Model\Data\Customer type. This entity stores non-static attributes not in _data, but _data[‘custom_attributes’] that causes the logical error in validation, so the rule doesn’t work. Let’s look at the solution to this problem.

How to Create Custom Rules in Magento 2 Using Amasty_RulesPro

In order to cope with this task, we should develop a plugin for the Amasty\RulesPro\Model\Rule\Condition\customer entity.

So, we determine the plugin in the di.xml file.

<type name="Amasty\RulesPro\Model\Rule\Condition\Customer">
        <plugin name="customer-validation" type="Web4pro\Corporate\Plugin\Customer" sortOrder="10"/>
    </type>

The plugin’s implementation is the following:

class Customer {

    protected $_customer;

    public function __construct(\Magento\Customer\Model\Customer $customer){
        $this->_customer = clone $customer;
    }

public function beforeValidate($conditionModel, $model){
        $customer = $model;
        if (!($customer instanceof \Magento\Customer\Model\Customer)) {
            $customer = $model->getQuote()->getCustomer();
            $attr = $conditionModel->getAttribute();

            $allAttr = $customer->__toArray();

            if ($attr == 'membership_days') {
                $allAttr[$attr] = $conditionModel->getMembership($customer->getCreatedAt());
            }
            if ($attr != 'entity_id' && !array_key_exists($attr, $allAttr)){
                $address = $model->getQuote()->getBillingAddress();
                $allAttr[$attr] = $address->getData($attr);
            }
            foreach($customer->getCustomAttributes() as $key=>$attribute){
                $allAttr[$key] = $attribute->getValue();
            }

            $customer = $this->_customer->unsetData()->addData($allAttr);
            return array($customer);
        }
       return array($model);
    }
}

This plugin implements a beforeValidate() method with two parameters: $conditionModel and $model. If the $model is not an entity of \Magento\Customer\Model\Customer type, it will be replaced by the \Magento\Customer\Model\Customer type with all attributes’ values which the entity should have for the particular custom shopping cart. Plugin method returns it to validate() method of the class for validation. As we can’t transfer ObjectManager to the plugin constructor, we transfer it to the entity of ObjectManager type. We clone it, clean up, and write all data of the current customer for the particular shopping cart.

How to Display Magento Sidebar of the Full Shopping Cart Using Ajax

Today we’d like to introduce the method of displaying the shopping cart’s sidebar after adding the product to the cart in Magento® 2. We are going to use AJAX for these purposes. You can also customize your online store’s shopping cart by performing this method if your shop is based on Magento.

Now Magento 2 has got a quite useful out-of-box feature: adding the product to the shopping cart with AJAX. You can turn on this option on the store setting page. This option works with all the products which don’t have file options.

Our task is to display the sidebar for a while after adding a product to the shopping cart. Here, the sidebar is a shopping cart block placed in the page title. We recommend you the specific module for doing this task.

<div class="ufy-block">
  <div class="ufy-block__wrapper">
    <p class="ufy-block__label">Useful for you:</p>
    <ul class="ufy-block__list">
      <li class="ufy-block__item"><a href="https://web4pro.net/blog-news/extension-attributes-for-total-model-in-magento-2/" target="_self" rel="noopener" data-wpel-link="internal">Extension Attributes for Total Model in Magento 2</a></li>
    </ul>
  </div>
</div>

Sidebar Implementation for Shopping Cart in Magento 2

Today we want to share the way how to solve this problem with our Magento AJAX tutorial. In Magento 2, the shopping cart is formed with a JavaScript plugin called the Knockout. The process is based on the shopping cart data which returns from the server with AJAX in JSON form. The browser stores these data until the next update. getSectionData() method of \Magento\Checkout\CustomerData\Cart class provides this JSON.

Let’s take a look at its implementation:

public function getSectionData()
    {
        $totals = $this->getQuote()->getTotals();
        return [
            'summary_count' => $this->getSummaryCount(),
            'subtotal' => isset($totals['subtotal'])
                ? $this->checkoutHelper->formatPrice($totals['subtotal']->getValue())
                : 0,
            'possible_onepage_checkout' => $this->isPossibleOnepageCheckout(),
            'items' => $this->getRecentItems(),
            'extra_actions' => $this->layout->createBlock('Magento\Catalog\Block\ShortcutButtons')->toHtml(),
            'isGuestCheckoutAllowed' => $this->isGuestCheckoutAllowed(),
            'website_id' => $this->getQuote()->getStore()->getWebsiteId()
        ];
    }

We should pay close attention to ‘extra_actions’ array element. It’s used for adding extra buttons to the sidebar, such as PayPal Express Checkout. But you can also add any block there, which implements \Magento\Catalog\Block\ShortcutInterface interface.

Step 1. Set up Delay Time Configuration

So let’s start. I’ll pass out creating registration.php and etc/module.xml files. We need these files only for registering the module. First, we set up delay time configuration using etc/adminhtml/system.xml file. Here is how it works:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
       <section id="web4pro_ajaxcart"translate="label"sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="1">
            <label>Ajax Cart</label>
            <tab>mg_extensions</tab>
            <resource>WEB4PRO_Ajaxcart::config</resource>
            <group id="general" translate="label" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>General</label>
                <field id="cart_add_delay" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Cart Add Popup Delay</label>
                </field>
            </group>
        </section>
    </system>
</config>

We should set the default value in the etc/config.xml module. Let’s take a 1 second as the default delay time.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../Store/etc/config.xsd">
    <default>
        <web4pro_ajaxcart>
            <general>
                <cart_add_delay>1</cart_add_delay>
            </general>
        </web4pro_ajaxcart>
    </default>
</config>

Step 2. Implementation of the Handlers for Two Events

Now we need to implement the handlers for two events. The first event is checkout_cart_add_product_complete which happens when the user successfully adds the product to the shopping cart. The second one is shortcut_buttons_container which can help us add the child block to the ‘extra_actions’ block. Let’s describe these events in the etc/events.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="shortcut_buttons_container">
        <observer name="ajaxcart" instance="Web4pro\Ajaxcart\Observer\Addshortcut" shared="false" />
    </event>
    <event name="checkout_cart_add_product_complete">
        <observer name="ajaxcart" instance="Web4pro\Ajaxcart\Observer\Addproduct" shared="false" />
    </event>
</config>

WEB4PRO\Ajaxcart\Observer\Addproduct class has the following implementation:

namespace Web4pro\Ajaxcart\Observer;
class Addproduct implements \Magento\Framework\Event\ObserverInterface {
    protected $_checkoutSession;
    public function __construct(\Magento\Checkout\Model\Session $session){
        $this->_checkoutSession = $session;
    }
    public function execute(\Magento\Framework\Event\Observer $observer){
        $this->_checkoutSession->setShowCart(true);
    }
}

As you can see, we get Magento\Checkout\Model\Session object in observer’s constructor. So if it’s processed successfully, we’ll set the session variable to ‘true’. It is necessary to do because in Magento 2, adding a product to the shopping cart and reloading the sidebar content always work in different AJAX requests from the browser to the server.

Step 3. Shortcut.phtml Block’s Template

Now, look at the jQuery AJAX request example. We implement WEB4PRO\Ajaxcart\Observer\Addshortcut class the next way:

namespace Web4pro\Ajaxcart\Observer;
class Addshortcut implements \Magento\Framework\Event\ObserverInterface {
    public function execute(\Magento\Framework\Event\Observer $observer){
        if($container = $observer->getEvent()->getContainer()){
           if($container->getRequest()->isAjax()){
               $block = $container->getLayout()->createBlock('\Web4pro\Ajaxcart\Block\Shortcut')
                   ->setTemplate('Web4pro_Ajaxcart::shortcut.phtml');
               $container->addShortcut($block);
           }
        }
    }
}

The container is a Magento\Catalog\Block\ShortcutButtons block where we place our child block using the addShortuct method. The block must implement \Magento\Catalog\Block\ShortcutInterface. Otherwise, it won’t be added. This event is triggered before the rendering Magento\Catalog\Block\ShortcutButtons block in the _beforeToHtml() method.

Let’s look at the \WEB4PRO\Ajaxcart\Block\Shortcut class implementation.

namespace Web4pro\Ajaxcart\Block;
class Shortcut extends \Magento\Checkout\Block\Cart\AbstractCart implements \Magento\Catalog\Block\ShortcutInterface {
    public function getAlias(){
        return 'cart_delay';
    }
    public function getCacheKeyInfo()
    {
        $result = parent::getCacheKeyInfo();
        $result[] = $this->_checkoutSession->getShowCart();
        return $result;
    }
    protected function _toHtml(){
    if($this->_checkoutSession->getShowCart()&&$this->getRequest()->getActionName()=='load'){
            $this->_checkoutSession->setShowCart(false);
            return parent::_toHtml();
        }
        return '';
    }
    public function getDelay(){
        return 1000*$this->_scopeConfig->getValue('magentice_ajaxcart/general/cart_add_delay');
    }
}

Used Methods

getDelay() method returns the loading time value in milliseconds.

getAlias() method allows us to implement \Magento\Catalog\Block\ShortcutInterface.

In __toHtml() method, we set the flag of displaying block to ‘false’ right after the output to avoid repeated block output and also implement its display only on the sidebar.

getCacheKeyInfo() method allows us to save the various blocks variants to the cache: a block with output and without one.

shortut.phtml block’s template looks the following way:

<script type="text/javascript">
    require(['Magento_Customer/js/customer-data'],function(customerData){
        customerData.reload(["cart"],false);
    });
    jQuery('.minicart-wrapper').addClass('active');
    jQuery('.minicart-wrapper a').first().addClass('active');
    jQuery('.minicart-wrapper div').first().show();
    setTimeout(function(){
        jQuery('.minicart-wrapper a').first().removeClass('active');
        jQuery('.minicart-wrapper div').first().hide();
        jQuery('.minicart-wrapper').removeClass('active');
    },<?php echo $this->getDelay(); ?>);
</script>

As you can see, this template content is just JavaScript. First, we set “active” class on the sidebar elements for displaying this class. After the timeout, we remove the class in order to hide the sidebar. The first-row code carries out cart content reload from the server.

You can use the shortcut block to place any other custom blocks to the sidebar.

<div class="ufy-block">
  <div class="ufy-block__wrapper">
    <p class="ufy-block__label">Useful for you:</p>
    <ul class="ufy-block__list">
      <li class="ufy-block__item"><a href="https://web4pro.net/blog-news/using-negative-values-product-quantity-in-magento-2/" target="_self" rel="noopener" data-wpel-link="internal">Using Negative Values for the Product Quantity in Magento 2</a></li>
      <li><a href="https://web4pro.net/blog-news/pricing-rules-in-magento-2/" target="_self" rel="noopener" data-wpel-link="internal">Creating Pricing Rules in Magento 2 Using Amasty_RulesPro</a></li>
    </ul>
  </div>
</div>

The Perfect Real Estate Agency Website

If you are a real estate agency, and you want to bring your business to a new level, or your goal is providing your customers with the most qualitative services, probably, you are looking for a receipt of the ideal real estate agency website. A full-featured website with the best user experience possible attracts a lot of customers and makes them stay with you for always. Are you agree? Just step into your customer’s shoes, and you’ll see how important the website is for users nowadays.

Today we reveal the elements of the perfect real estate agency website. We are going to share some features that bring success to this sphere, and you’ll learn how to create the ideal website.

First of all, let’s try to figure out what expectations and problems have your customers.

User’s Expectations of the Real Estate Agency Website

There are two types of real estate agency’s clients. They are those who sell and those who buy.

Those Who Buy

Let’s imagine that you are looking for some property in the specific area. What factors usually affect you while choosing the estate? They could be the following:

  • the lack of time;
  • an ability/disability to relocate and take part in meetings for inspecting the property;
  • some money limits;
  • the level of credibility to an agency;
  • customer’s service quality, the personal attitude;
  • freedom of choice (perhaps, you always consider several variants of estate):
  • location and surrounding areas;
  • the volume of relevant information about the property;
  • some additional information and useful advice.

So, as you can see, buyers face a lot of issues, such as the lack of time for tripping, searching, and communicating. That’s why they must find all answers to these questions in one place – your website.

Those Who Sell

The same goes to sellers. They should base on the factors we considered above. The sellers must predict and answer all possible buyers’ questions that can arise. They need to share the relevant information about estate they sell. This way, the sellers can win the buyers’ trust.

But at the same time, the sellers have the similar problem: the lack of time for tripping, searching, and communicating.

And what about you, the real estate agent? Haven’t you the same pitfalls that sometimes prevent you from dealing with the right people? All of us lack time, but our modern world requires a lot of our efforts. We can cope with all these things and solve the tasks of three parts in one time by creating the perfect real estate agency’s website.

A good website must solve the both parts’ problems: the customer’s and business’s.Our task is to look at our customer’s expectations, consider how we can answer these possible question, and how we can implement the features on the website that is easy to use and pleasant to visit.

Real Estate Agency Website Template

Why do we call it template? Don’t take it too literally, because there are no templates and limits for good and interesting solution. We just base on our project experience in the real estate sphere. We came up with some ideas of the ideal real estate agency’s website. It should include some special features, basic functionality, and necessary pages. We call it “template” because it always works good but looks absolutely differently for all projects.

First, let’s make a decision about the powerful base for the website. In most cases, Drupal or WordPress CMSs will be for this website type.

Then let’s think about the structure.

Real Estate Agency Website Functionality

About me/us Page

You should tell the world about your business. Place the relevant information on this page.

Property to Buy

This place is for buyers who look for properties. They will find a wide variety of properties on this page.

Property to Sell

Help the sellers make deals with buyers. They can sell their properties using your website.

Description Page for Each Property

Your customers provide the relevant information on their properties, share photos, and contacts on the specific pages.

Clients and Testimonials

Trust and again trust. This place is dedicated to earning customer loyalty and showing clients’ reviews.

User Cabinet

It is a good way to show your respect to website visitors, make their experience much better, and at the same time get some data for the future interaction. It also secures the user’s data and helps save data related to deals, viewed properties, and some other useful information.

FAQ

Answers to frequently asked questions save your and customers’ time and help them get something that they look for immediately.

Blog and News

Adds relevant information about your services, includes useful advice for clients, keep them in touch with you and also growth their trust. By the way, a blog with qualitative content is good for SEO and appreciated by search engines.

Contact Page

Gives all your contacts for keeping in touch with all your website visitors and potential customers. Be reachable from any place in the world.

Real Estate Website Features

As for features, they help us to get the best possible user experience and make the website user-friendly.

Here are the must-have features for the real estate website:

Mortgage Calculator

Allows accounting the mortgage for buyers.

Property Listing

Your website is a catalog. So it should include the listing that is comfortable to use.

Property Comprehension

It’s a very helpful feature for website users. When you have some variants, you want to compare them. Give this ability to your customers.

Save, Print, and Download Brochures

If we talk about properties, interaction with the offline brochure is important when you want to ask somebody for a piece of advice or in case you need time to make a decision.

Virtual Tour

It’s our future. Virtual tours allow getting to any place from any point of the planet using only the computer and the Internet. Show how your properties look in reality, and your customers will trust you more.

Featured, New, Sold Properties

The customers should track the progress on selling properties and adding new opportunities. Provide them with these useful features.

Filters

Filters by location, cost, and other factors are the indicators of advanced user experience. Make your website super-user-friendly and modern.

How You Can Get The Perfect Real Estate Agency Website

Finally, we move to the key point. How can you get the ideal website? Let’s summarize:

  • prepare relevant and high-quality content for your website;
  • figure out your requirements and goals;
  • consider the useful features you want to implement;
  • trust us your project, and we’ll develop the perfect real estate agency website for your business success!

Meet Google’s Invisible reCAPTCHA!

Finally, going through many years of experience, Google developed the new generation of reCAPTCHA. Now, you shouldn’t enter the text from the image, choose the image elements, or even tick the box in order to prove that you are not a robot. Now reCAPTCHA is invisible! So it doesn’t require any additional actions for checking the user. Let’s look how it worked before, and tell the difference.

How reCAPTCHA Actually Works

It’s safe to say that CAPTCHA is a great technology for protecting the website from hacking. It is used for detecting robots and spammers by the processes that check the people’s sensor systems and brain work, like an ability to understand the images, listen out recordings, and structure the information by some logic (like choosing the pictures with trees and analyze the question).

Here is how it looks:

google invisible reCAPTCHA

The Main Purpose of reCAPTCHA

CAPTCHA or Completely Automated Public Turing test to tell Computers and Humans Apart, is a technology that allows telling the difference between bots and the people. It is used for reducing the number of spam attacks and protecting the websites. CAPTCHA which asks the user to tell what is shown in the image is the most popular type nowadays. It successfully coped with this task for many years.

As far as computer and technologies become smarter than before, sometimes the computer catches encrypted senses even better than the human eye does. Now the computers are trusted to read and detect some hardly-visible letters on the ancient masterpieces and archaeological trophies.

At the same time, images are not a problem for computers. They have already begun to understand pictures. Search engines like Google can perform the search by images. Therefore, Google artificial intelligence goes forward and is developed day by day.

As you can see, the thing is that it causes some other issue: machines become smarter, and the interactive reCAPTCHA is not enough. Bots can outsmart it.

New Google’s Invisible reCAPTCHA

Google refused the traditional interactive reCAPTCHA. It developed new generation invisible reCAPTCHA that analyzes the user’s behavior. It will analyze the movements of user’s mouse and IP-address. The thing is that bots usually move the cursor using the shortest way. But people can’t do this and don’t want.The new reCAPTCHA looks like a little box which Google ticks by itself and tells you that you are not a robot. Congratulations 🙂

Our WEB4RO team has developed a lot of websites secured with reCAPTCHA. We highly recommend not to pass up this new technology and add it to your website via API. You can find reCAPTCHA here. If you need some help, please, contact us. We’ll provide your website with a new invisible reCAPTCHA.

Keep up with the times and watch your website!