Customizing Google Snippets with Microdata


Zen Cart is certainly powerful software, but it’s prowess is the dynamic platform of functions in PHP which are usable in many different calls from different areas of the template. Social media has been a challenge for Zen Cart, specifically pointing out the product image properly for social sharing. Because Zen Cart has many different page templates and only one real <head> template… the challenge for making Google + for example select the product image has been a rough road.

However, Microdata, from Schema.org, Good Relations and Rich Snippets opens the door for us. Now Facebook has their own system, but should support Microdata as well. Schema.org is a universal project which provides a collection of markup tags to markup pages in ways recognized by major search engines. Search engines including Bing, Google, Yahoo! and Yandex rely on this system to improve the display of search results, making it easier for people to search and find web pages.

In this post I will show you how to implement the Product Schema Microdata for your product pages. Now this tutorial is for 1.5.0, but if you’re sharp you can easily implement it in other Zen Cart versions.

 

newsnippets

So the applicable tags we have available currently for Products are as follows:

  • name – name of the product.
  • description – A short description of the item.
  • image – URL of an image of the item.
  • price – The price of the product. A floating point number. You may use either a decimal point (‘.’) or a comma (‘,’) as a separator.
  • currency – The currency used to describe the product price, in three-letter ISO format.
  • quantity – Number in stock
  • availability – out_of_stock, in_stock, instore_only or preorder
  • brand/manufacturer – The brand of the product.
  • condition – new, used or refurbished.
  • identifier – asin, isbn, mpn, sku or upc.
  • breadcrumb – Bread crumbs, path to category
  • category – The product category—for example, “Books-Fiction“, “Tools“, or “Cars“. You can include multiple categories. Any value is accepted, but Google recognizes the categories described in this article.
  • seller – The seller of the product. Can contain a Person or Organization.
  • offer language markup is included via your Zen Cart meta tag (lang=”en“). This is not microdata markup any longer, as it should never have been =)
  • payment methods
  • delivery lead time
  • offer delivery area

!!!!UPDATE   11/30/2016 !!!!!

If you are still using this the breadcrumbs instructions have changed below.

!!!!UPDATE   2/3/2015 !!!!!

I have developed a module that nearly anyone can install that does this and much more. It’s BETA, but give it a shot HERE

!!!!UPDATE   2/3/2015 !!!!!

*** Update 1/29/2013 to remove (itemscope itemtype=”http://schema.org/Product“) from html_header.php and move more towards Microdata markup more suitable for products and added breadcrumbs and category tags.

*** Update 3/21/2013 Added offer language and submission tool for Google

*** Update 4/17/2013 Added Payments Accepted, Delivery Lead Time and Delivery Area

***Update 10/25/2013 solved specials issue for pricing

Implementation

————————————————————————————————————

Using the identifier for your product’s image.

This method, while imperfect will include all additional images and attribute images. The imperfect part is that it will include cross sell images (if installed) and also purchased etc. The second optional method will include ONLY the main product image, but no additional product images AND has some struggle with Image Handler (if installed). They both work fine with Zen Lightbox (if installed). The second option produces results less than desirable, and for now we encourage you to use the first.

First Option In includes/functions/html_output.php line 199

change the 2cnd image tag

$image = ‘<img src=”‘ . zen_output_string($src) . ‘” alt=”‘ . zen_output_string($alt) . ‘”‘;

to

$image = ‘<img itemprop=”image” src=”‘ . zen_output_string($src) . ‘” alt=”‘ . zen_output_string($alt) . ‘”‘;

Second Option In includes/templates/your_template/tpl_modules_main_product_image.php

find 2 instances of  

<span class=”imgLink”>

and change to

<span  itemprop=”image” class=”imgLink”>

If you are using Zen Lightbox you can do the following with a little better results for option 2.

find

$rel . ‘” title=”‘ . addslashes($products_name) . ‘”>

and change to

$rel . ‘” title=”‘ . addslashes($products_name) . ‘” itemprop=”image”>

————————————————————————————————————

Using the identifier for your product’s name.

In includes/templates/your_template/templates/tpl_product_info_display.php

Locate your product name

<h1 id=”productName”><?php echo $products_name; ?></h1>

Now wrap it in the property name tag

<h1 id=”productName”><span itemprop=”name“><?php echo $products_name; ?></span></h1>

—————————–

This method is the OLD way, and perfectly acceptable if you don’t use a great deal of specials.

Using the identifier for your product’s price & currency type. Note: We have added only 1 currency value, not multiple.

Locate your price

<h2 id=”productPrices”>
<?php
// base price
if ($show_onetime_charges_description == ‘true’) {
$one_time = ‘<span >’ . TEXT_ONETIME_CHARGE_SYMBOL . TEXT_ONETIME_CHARGE_DESCRIPTION . ‘</span><br />’;
} else {
$one_time = ”;
}
echo $one_time . ((zen_has_product_attributes_values((int)$_GET[‘products_id’]) and $flag_show_product_info_starting_at == 1) ? TEXT_BASE_PRICE : ”) . zen_get_products_display_price((int)$_GET[‘products_id’]);
?></h2>

Now we will wrap it in the price property

<h2 id=”productPrices”>
<?php
// base price
if ($show_onetime_charges_description == ‘true’) {
$one_time = ‘<span >’ . TEXT_ONETIME_CHARGE_SYMBOL . TEXT_ONETIME_CHARGE_DESCRIPTION . ‘</span><br />’;
} else {
$one_time = ”;
}
echo $one_time . ((zen_has_product_attributes_values((int)$_GET[‘products_id’]) and $flag_show_product_info_starting_at == 1) ? TEXT_BASE_PRICE : ”) . ‘<span itemprop=”offerDetails” itemscope itemtype=”http://data-vocabulary.org/Offer”><meta itemprop=”currency” content=”USD”/><span itemprop=”price”>’ . zen_get_products_display_price((int)$_GET[‘products_id’]) . ‘</span></span>’;
?></h2>

—————————–

Prices markup NEW way, no errors. Before you do this, to better understand, please read this post 

So I have been struggling with this for NO good reason. There is NO good reason to include the specials prices at all! Google caches this information, so we should be just giving them the product’s regular price anyhow.

Find

<ul id=”productDetailsList” style=”font-size:11px;”>

And somewhere in your list add this list item.

<li>Products Regular Price: $<span itemprop=”offerDetails” itemscope itemtype=”http://data-vocabulary.org/Offer”><meta itemprop=”currency” content=”USD”/><span itemprop=”price”><?php echo $products_price = zen_get_products_base_price($product_info->fields[‘products_id’]); ?></span></span></li>

—————————–

Only accept 1 currency? You can use a Meta Tag to set your currency markup.

In includes/templates/your_template/common/html_header.php

Find

<meta http-equiv=”imagetoolbar” content=”no” />

and on the next line add

<meta itemprop=”currency” content=”USD” />

—————————–

Using the identifier for your product’s description.

 Locate your product description

<div id=”productDescription”><?php echo stripslashes($products_description); ?></div>

and wrap it in the description property tag

<div id=”productDescription”><span itemprop=”description”><?php echo stripslashes($products_description); ?></span></div>

—————————–

Using the identifier for your product’s brand/manufacturer.

Locate the manufacturer’s function

<?php echo (($flag_show_product_info_manufacturer == 1 and !empty($manufacturers_name)) ? ‘<li>’ . TEXT_PRODUCT_MANUFACTURER . $manufacturers_name . ‘</li>’ : ”) . “\n”; ?>

now you will wrap it in either the brand or manufacturer property tag, whichever is most appropriate for your cart

<?php echo (($flag_show_product_info_manufacturer == 1 and !empty($manufacturers_name)) ? ‘<li>’ . TEXT_PRODUCT_MANUFACTURER . ‘<span itemprop=”brand”>’ . $manufacturers_name . ‘</span></li>’ : ”) . “\n”; ?>

—————————–

Using the identifier model. Locate your model number line.

<?php echo (($flag_show_product_info_model == 1 and $products_model !=”) ? ‘<li>’ . TEXT_PRODUCT_MODEL . $products_model . ‘</li>’ : ”) . “\n”; ?>

Now we will wrap it in the mpn (manufacturers part number) identifier property

<?php echo (($flag_show_product_info_model == 1 and $products_model !=”) ? ‘<li>’ . TEXT_PRODUCT_MODEL . ‘<span itemprop=”identifier” content=”mpn:’  .  $products_model . ‘”>’ . $products_model . ‘</span></li>’ : ”) . “\n”; ?>

—————————–

Using the quantity identifier. Locate your quantity line.

<?php echo (($flag_show_product_info_quantity == 1) ? ‘<li>’ . $products_quantity . TEXT_PRODUCT_QUANTITY . ‘</li>’  : ”) . “\n”; ?>

Now we will wrap it in the quantity property

<?php echo (($flag_show_product_info_quantity == 1) ? ‘<li>’ . ‘<span itemprop=”quantity”>’ . $products_quantity . ‘</span>’ .  TEXT_PRODUCT_QUANTITY . ‘</li>’  : ”) . “\n”; ?>

—————————–

Using the Availability property

Just a quick way to dynamically deliver the value of in stock our not from quantity.

In the list you just edited for model and such locate the closing </ul> tag and on the line right above it add the following. Acceptable values are (in_stock, out_of_stock, instore_only or preorder)

<?php if ($products_quantity > 0) { ?><li><span itemprop=”availability” content=”in_stock”>In Stock</span></li><?php }?><?php if ($products_quantity == 0) { ?><li><span itemprop=”availability” content=”out_of_stock”>Out of Stock</span></li><?php }?>

—————————–

Using the Condition property

We will fake this for a vanilla Zen Cart as well by adding another line just above the closing </ul> as in the previous instruction. Values which can be used are (new, used and refurbished)

<li><span itemprop=”condition” content=”new”>New</span></li>

Lastly to make this markup most flexible for search engines and other platforms to understand locate the opening div for your product content in tpl_product_info_display.php

—————————–

Using Rich Snippets to identify the category

the section marked details, find

</ul>
<br />
<?php
}
?>
<!–eof Product details list –>

Just before this line add

<li>Category: <?php echo ‘<span itemprop=”category” content=”‘ . $categories->fields[‘categories_name’] . ‘”>’ . $categories->fields[‘categories_name’] ; ?></li>

—————————–

Using Rich Snippets to identify the payment methods you accept. The syntax provided is for Visa, MasterCard, Discover and PayPal. Other methods can be found here (http://www.heppnetz.de/ontologies/goodrelations/v1#PaymentMethod)

the section marked details, find

</ul>
<br />
<?php
}
?>
<!–eof Product details list –>

Just before this line add

<li>Payments Accepted: <span itemprop=”http://purl.org/goodrelations/v1#acceptedPaymentMethods” href=”http://purl.org/goodrelations/v1#PayPal” />PayPal</span>, <span itemprop=”http://purl.org/goodrelations/v1#acceptedPaymentMethods” href=”http://purl.org/goodrelations/v1#VISA” />Visa</span>, <span itemprop=”http://purl.org/goodrelations/v1#acceptedPaymentMethods” href=”http://purl.org/goodrelations/v1#MasterCard” />MasterCard</span>, <span itemprop=”http://purl.org/goodrelations/v1#acceptedPaymentMethods” href=”http://purl.org/goodrelations/v1#Discover” />Discover</span></li>

—————————–

Using Rich Snippets to identify the general delivery lead time for your products

the section marked details, find

</ul>
<br />
<?php
}
?>
<!–eof Product details list –>

Just before this line add

<li>Delivery Lead Time: <span itemprop=”deliveryLeadTime”>21 days</span></li>

—————————–

Using Rich Snippets to identify the seller (you) for your products. Unless you are running a multi-seller product catalog, this is unnecessary and redundant

the section marked details, find

</ul>
<br />
<?php
}
?>
<!–eof Product details list –>

Somewhere in this list add

<li><b>Available at:</b> <span itemprop=”seller”>Your Name</span></li>

or

<li><b>Available at:</b> <span itemprop=”seller”><?php echo (STORE_NAME );  ?></span></li>

—————————–

Using Rich Snippets to identify the shipping region(s) for your products. Note that the proper 2 letter country code must be used for the content=””, but the text following can be express however you like.

the section marked details, find

</ul>
<br />
<?php
}
?>
<!–eof Product details list –>

Just before this line add

<li>Shipping to: <span itemprop=”eligibleRegion” content=”US”>United States</span> &amp; <span itemprop=”eligibleRegion” content=”CA”>Canada</span></li>

—————————–

Setting up the container for the product schema

Find

<div id=”productGeneral”>

and add this to it

itemscope itemtype=”http://data-vocabulary.org/Product”

So it looks like this

<div id=”productGeneral” itemscope itemtype=”http://data-vocabulary.org/Product”>

————————————————————————————————————

Adding the breadcrumb identifier for Rich Snippets

In includes/classes/breadcrumb.php on line 57

Find

if ($this->_trail[$i][‘title’] == HEADER_TITLE_CATALOG) {
$trail_string .= ‘  <a href=”‘ . HTTP_SERVER . DIR_WS_CATALOG . ‘”>’ . $this->_trail[$i][‘title’] . ‘</a>’;
} else {
$trail_string .= ‘  <a href=”‘ . $this->_trail[$i][‘link’] . ‘”>’ . $this->_trail[$i][‘title’] . ‘</a>’;

and change it to

if ($this->_trail[$i]['title'] == HEADER_TITLE_CATALOG) {
          $trail_string .= '  <span vocab="http://schema.org/" typeof="BreadcrumbList"><span property="itemListElement" typeof="ListItem"><a href="' . HTTP_SERVER . DIR_WS_CATALOG . '">' . $this->_trail[$i]['title'] . '</a></span></span>';
        } else {
          $trail_string .= '  <span property="itemListElement" typeof="ListItem"><a property="item" typeof="WebPage" href="' . $this->_trail[$i]['link'] . '"><span property="name">' . $this->_trail[$i]['title'] . '</span></a></span>';

This is no longer required, reverse it in your file and just use the edit right above.

Then in includes/templates/your_template/common/tpl_main_page.php

Find

<div id=”navBreadCrumb”><?php echo $breadcrumb->trail(BREAD_CRUMBS_SEPARATOR); ?></div>

and change it to

<div id=”navBreadCrumb” itemscope itemtype=”http://data-vocabulary.org/Breadcrumb”><?php echo $breadcrumb->trail(BREAD_CRUMBS_SEPARATOR); ?></div>

————————————————————————————————————

Now go over here and let Google see if they can read your changes properly using the Structured Data Testing Tool. Check our testing site here in the tool. Once you have tested your snippets Google now provides a means to let them know you are using rich snippets by submitting this form. The nice part is that they will respond and note any issues they find to better help you come to completeness.

There are more formats for the product schema that we will be looking to incorporate such as review, aggregateRating and offers. Stay tuned, we will update when this project has changes. These items weren’t initially included as they require a module to display in the product page or the data output is wrong for the Schema, so there will be some core changes and such to incorporate these additional properties for Zen Cart.


55 responses to “Customizing Google Snippets with Microdata”

  1. What I’m saying is whether they are in stock or not the stock number (level) is an indexed value. So it should always be present.

    Understand.. However I have a few clients that for a variety of business reasons, do not want the quantity in stock to be displayed. So we have turned it off using the Product Types settings in the admin.. Just so that I am clear, you are not saying/suggesting that this is going to be problematic for them to keep this setting??

  2. Hi Melanie.. Sorry to be a PITA.. **lol**

    In the article you indicate that the following tags for products are available.

    ~ seller – The seller of the product. Can contain a Person or Organization.
    ~ offer language markup

    I do not see these covered in this tutorial though. Are they available only through your premium Google Snippets with Microdata premium service??

  3. Google and the other formats removed offer_language… it really should have never been =) I added seller, but it’s mostly redundant

  4. Google and the other formats removed offer_language… it really should have never been =) I added seller, but it’s mostly redundant

    Thanks.. regarding seller, I figured as much, but thought I’d ask anyway..

    On another note, I created a module based on this tutorial.

    My primary purpose for creating this for myself was to standardize my own future client implementations of your tutorial and provide myself a means to configure some of the specific options from the admin to make it easy on myself. I don’t have any real plans to submit it to the Zen Cart downloads at the moment. I may reconsider my position on this at some point.. Though I have used it on one client so far, I am still tweaking and testing this so it should still be considered BETA. I can report that so far this works and I’ve tested the heck outta this.. (your mileage may vary..)

    So just in case there is any interest in what I have done, I am happy to share it with the community and have posted it to Github.
    https://github.com/DivaVocals/zen_Google-Snippets-with-Microdata

  5. I specifically didn’t make a module as most of them are included in 1.6 release. I really worry about mods that are unnecessary because they cause delays and upgrade issues going forward =)
    But I don’t mind sharing it and appreciate the hard work. Sorry for the delay the flu has been trying to kill me.