Laut dem Service eMarketer steigt der Verkauf auf dem E-Commerce-Markt in diesem Jahr sogar um 20,1% und soll 1,5 Trilliarden Dollar betragen. Kein Wunder also, dass die Unternehmen immer mehr in Internet-Verkauf investieren und dass immer wieder neue Shops im Netz wie Pilze aus dem Boden schießen. In dieser ganzen Verwirrung begrenzen sich die Benutzer darauf, die Angebote nur oberflächlich zu überprüfen und die Eigentümer der Internetshops müssen für eine gute SEO sorgen. Kämpfen wir also um den Kunden und schreiben wir zusammen eine Erweiterung für Magento, die den potenziellen Kunden die wichtigsten Informationen zum Produkt an einer dafür bestimmten Stelle zeigt.
Zwar bevorzugen alle populären Suchmaschinen Daten im Format Microdata, wir aber möchten die Vorlagendateien unseres Shops nicht bearbeiten und übergeben die Daten deshalb über JSON+LD (JSON Linked Data), ohne diese zu berühren. Wenn Sie keine Zeit dafür haben, eine eigene Erweiterung zu schreiben (oder auch keine Lust dazu ;)), finden Sie in dem weiteren Teil des Artikels ein einsatzbereites Modul.
Was möchten wir übergeben?
Die strukturellen Daten können an die Suchmaschine viele wichtige Daten von Seiten unterschiedlichen Typs übergeben: Kochrezepte, TV Programm, Angaben der Organisation. Wir konzentrieren uns auf den Kern unserer Tätigkeit, also auf das Produkt. In den Ergebnissen möchten wir also folgende Daten berücksichtigen:
- Name
- SKU
- Foto des Produkts
- kürze Beschreibung
- URL
- Verfügbarkeit
- Preis
- Währung
- Kategorie
- Farbe
- Marke
- Model
- Anzahl der Kommentaren (optional)
Modulstruktur
ACHTUNG!!! Rich Snippets sollten nicht in Shops implementiert werden, in denen die Preise oft geändert werden. Die Suchmaschinen indizieren unsere Produkte nicht tagtäglich, deshalb könnten die Daten in Suchergebnissen nicht aktuell sein.
Den Shop auf unserem localhost starten, Backup der Datenbank machen, Cache in Magento deaktivieren und das Skelett des Zusatzes bilden:
<?php class Creativestyle_Richsnippets_Block_Jsonld extends Mage_Core_Block_Template {
}
<?xml version="1.0"?>
<config>
<modules>
<Creativestyle_Richsnippets>
<version>1.0.0</version>
</Creativestyle_Richsnippets>
</modules>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<config>
</config>
<?xml version="1.0"?>
<config>
<modules>
<Creativestyle_Richsnippets>
<active>true</active>
<codePool>local</codePool>
</Creativestyle_Richsnippets>
</modules>
</config>
<?php
class Creativestyle_Richsnippets_Helper_Data extends Mage_Core_Helper_Abstract {
}
In dieser Phase soll unser Zusatz schon funktionieren. Suchen wir ihn also in der Modulliste in der Systemsteuerung: System > Configuration > Advanced > Advanced
Konfigurationsformular
Geben wir etwas Code dazu, um eigene Position in der Konfiguration des Systems Magento und ein paar Optionen zwecks Steuerung unseres Zusatzes zu bilden:
/app/code/local/Creativestyle/Richsnippets/etc/system.xml<?xml version="1.0" encoding="UTF-8"?>
<config>
<tabs>
<creativestyle translate="label">
<label>Creativestyle</label>
<sort_order>220</sort_order>
</creativestyle>
</tabs>
<sections>
<richsnippets translate="label">
<label>Rich Snippets</label>
<tab>creativestyle</tab>
<sort_order>130</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<groups>
<general translate="label">
<class>open</class>
<label>General</label>
<sort_order>0</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<fields>
<enabled translate="label">
<label>Enable Rich Snippets</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>10</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</enabled>
<review translate="label comment">
<label>Enable Review & Rating data</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>20</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[If you are not using reviews in your project, please disable this option for better performance.]]></comment>
</review>
</fields>
</general>
<attributes translate="label comment">
<label>Attributes</label>
<sort_order>100</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment><![CDATA[<p style="padding:15px 10px; border: 1px solid #e1e1e1; background: #fff;">If you're using different attributes for the ones listed below, please select them here so I could show the right content for the search engines.</p>]]></comment>
<fields>
<manufacturer translate="label">
<label>Attribute for 'Manufacturer'</label>
<frontend_type>select</frontend_type>
<source_model>richsnippets/attributes</source_model>
<sort_order>110</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</manufacturer>
<color translate="label">
<label>Attribute for 'Color'</label>
<frontend_type>select</frontend_type>
<source_model>richsnippets/attributes</source_model>
<sort_order>120</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</color>
<model translate="label">
<label>Attribute for 'Model'</label>
<frontend_type>select</frontend_type>
<source_model>richsnippets/attributes</source_model>
<sort_order>130</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</model>
</fields>
</attributes>
</groups>
</richsnippets>
</sections>
</config>
Die Sektion <tabs> informiert Magento, dass wir eine eigene Position in dem Konfigurationsteil des Shops bilden möchten, <groups> hilft uns, in unserem Formular Ordnung zu halten. Folgend haben wir folgende Optionen zugegeben:
- <enabled>…</enabled> - das gesamte Modul ein-/ausschalten
- <review>…</review> - entscheidet, ob die Informationen zu Preisen und Meinungen übersendet werden sollten
- <manufacturer>…</manufacturer> - viele Shops verwenden Zusätze z.B. Shop By Brands, die auf anderen (eigenen) Attributen zur Bedienung von Produkten basieren. Dieses Feld ermöglicht, auf dieses Attribut zu verweisen, was uns davor schützt, die Marke des Produktes an vielen Orten während der Hinzufügung des Produktes zu pushen.
- <color>…</color> - s.o.
- <model>…</model> - standardmäßig bedient Magento solch ein Attribut nicht. Google erlaubt jedoch, das Model des gegebenen Produktes anzuzeigen, wenn Sie also in Ihrem Shop über solch ein Attribut verfügen, können Sie hier darauf verweisen. Falls nicht - können Sie es jederzeit bilden.
Wir sollten jedoch bemerken, dass das Attribut <source_model> auf das Pfad richsnippets/attributes. verweist. Das Anlegen von einer neuen Datei ist also erforderlich - einem Modell, mit dem alle zugänglichen Attribute gepusht werden können:
/app/code/local/Creativestyle/Richsnippets/Model/Attributes.php<?php
class Creativestyle_Richsnippets_Model_Attributes
{
public function toOptionArray()
{
$attributes = Mage::getResourceModel('catalog/product_attribute_collection')
->addVisibleFilter()
->addFieldToFilter('frontend_input', array('text', 'select', 'textarea'));$attributeArray[] = array(
'label' => '-',
'value' => ''
);foreach ($attributes as $attribute) {
$attributeArray[] = array(
'label' => $attribute->getData('frontend_label'),
'value' => $attribute->getData('attribute_code')
);
}return $attributeArray;
}
}?>
Die Variable $attributeArray beinhaltet jetzt eine Tafel mit allen Attributen Typ „text”, „select” und „textarea”, erweitert um eine Option ohne Wert (standardmäßig).
Fügen wir unser neues Modell, samt Helper und Block, in die Konfigurationsdatei hinzu und ergänzen wir es um die Sektion global:
/app/code/local/Creativestyle/Richsnippets/etc/config.xml<?xml version="1.0"?>
<config>
<modules>
<Creativestyle_Richsnippets>
<version>1.0.0</version>
</Creativestyle_Richsnippets>
</modules>
<global>
<models>
<richsnippets>
<class>Creativestyle_Richsnippets_Model</class>
</richsnippets>
</models>
<blocks>
<richsnippets>
<class>Creativestyle_Richsnippets_Block</class>
</richsnippets>
</blocks>
<helpers>
<richsnippets>
<class>Creativestyle_Richsnippets_Helper</class>
</richsnippets>
</helpers>
</global>
</config>
Um einige Änderungen in der Administrations-Bereich zu sehen, müssen wir, um das Menü auf Hinzufügen klicken. Schreiben wir deshalb nach den <global> Abschnitt der folgenden Regel:
<adminhtml>
<acl>
<resources>
<admin>
<children>
<system>
<children>
<config>
<children>
<richsnippets>
<title>Rich Snippets</title>
</richsnippets>
</children>
</config>
</children>
</system>
</children>
</admin>
</resources>
</acl>
</adminhtml>
Und fertig! Jetzt können wir unseren neuen Zusatz vom Control Panel verwalten! Also noch einmal aus- und einloggen um die Privilege neu zu laden (sonst kriegen wir anstatt unseres Formulars 404 zu sehen) und System > Configuration öffnen. Hier sollte sich ein neuer Tab mit dem Namen Creativestyle zeigen. Noch ein paar Standardeinstellungen für unsere Felder am Ende der Datei und die Information über die Vorlagendatei hinzufügen:
<frontend>
<layout>
<updates module="Creativestyle_Richsnippets">
<richsnippets>
<file>richsnippets.xml</file>
</richsnippets>
</updates>
</layout>
</frontend><default>
<richsnippets>
<general>
<enabled>1</enabled>
<review>1</review>
</general>
<attributes>
<manufacturer>manufacturer</manufacturer>
<color>color</color>
<model></model>
</attributes>
</richsnippets>
</default>
Nach der Speicherung der Änderungen sollen wir den Endeffekt des Backends zu sehen bekommen.
Eine Tafel mit Produktdaten erstellen
Mit dem fertigen Formular können wir uns auf den Modulkern konzentrieren. Wir ergänzen unseren Block um ein paar Funktionen, die uns zum Schluss eine fertige Tabelle mit strukturellen Daten anhand der Schemata http://schema.org liefern sollten, und genauer gesagt um einen Typ – Produkt
<?phpclass Creativestyle_Richsnippets_Block_Jsonld extends Mage_Core_Block_Template
{
public function getProduct()
{
$product = Mage::registry('current_product');
return ($product && $product->getEntityId()) ? $product : false;
}public function getAttributeValue($attr)
{
$value = null;
$product = $this->getProduct();
if($product){
$type = $product->getResource()->getAttribute($attr)->getFrontendInput();
if($type == 'text' || $type == 'textarea'){
$value = $product->getData($attr);
}elseif($type == 'select'){
$value = $product->getAttributeText($attr);
}
}
return $value;
}
}?>
Kurz und bündig:
- die Funktion getProduct() überprüft, ob wir uns auf der Seite des Produktes befinden
- mit der Übergabe des Parameters als Attributcode an die Funktion getAttributeValue() erhalten wir dessen Wert
Zum Schluss schreiben wir eine Funktion dazu, die alle Daten über das Produkt in einer Tabelle sammelt und im Format JSON zurückliefert.
Das ist der komplette Inhalt des Blocks:
<?phpclass Creativestyle_Richsnippets_Block_Jsonld extends Mage_Core_Block_Template
{
public function getProduct()
{
$product = Mage::registry('current_product');
return ($product && $product->getEntityId()) ? $product : false;
}public function getAttributeValue($attr)
{
$value = null;
$product = $this->getProduct();
if($product){
$type = $product->getResource()->getAttribute($attr)->getFrontendInput();if($type == 'text' || $type == 'textarea'){
$value = $product->getData($attr);
}elseif($type == 'select'){
$value = $product->getAttributeText($attr) ? $product->getAttributeText($attr) : '';
}
}
return $value;
}public function getStructuredData()
{
// get product
$product = $this->getProduct();// check if $product exists
if($product){
$categoryName = Mage::registry('current_category') ? Mage::registry('current_category')->getName() : '';
$productId = $product->getEntityId();
$storeId = Mage::app()->getStore()->getId();
$currencyCode = Mage::app()->getStore()->getCurrentCurrencyCode();$json = array(
'availability' => $product->isAvailable() ? 'http://schema.org/InStock' : 'http://schema.org/OutOfStock',
'category' => $categoryName
);// check if reviews are enabled in extension's backend configuration
$review = Mage::getStoreConfig('richsnippets/general/review');
if($review){
$reviewSummary = Mage::getModel('review/review/summary');
$ratingData = Mage::getModel('review/review_summary')->setStoreId($storeId)->load($productId);// get reviews collection
$reviews = Mage::getModel('review/review')
->getCollection()
->addStoreFilter($storeId)
->addStatusFilter(1)
->addFieldToFilter('entity_id', 1)
->addFieldToFilter('entity_pk_value', $productId)
->setDateOrder()
->addRateVotes()
->getItems();$reviewData = array();
if (count($reviews) > 0) {
foreach ($reviews as $r) {
foreach ($r->getRatingVotes() as $vote) {
$ratings[] = $vote->getPercent();
}$avg = array_sum($ratings) / count($ratings);
$avg = number_format(floor(($avg / 20) * 2) / 2, 1); // average rating (1-5 range)$datePublished = explode(' ', $r->getCreatedAt());
// another "mini-array" with schema data
$reviewData[] = array(
'@type' => 'Review',
'author' => $this->htmlEscape($r->getNickname()),
'datePublished' => str_replace('/', '-', $datePublished[0]),
'name' => $this->htmlEscape($r->getTitle()),
'reviewBody' => nl2br($this->escapeHtml($r->getDetail())),
'reviewRating' => $avg
);
}
}// let's put review data into $json array
$json['reviewCount'] = $reviewSummary->getTotalReviews($product->getId());
$json['ratingValue'] = number_format(floor(($ratingData['rating_summary'] / 20) * 2) / 2, 1); // average rating (1-5 range)
$json['review'] = $reviewData;
}// Final array with all basic product data
$data = array(
'@context' => 'http://schema.org',
'@type' => 'Product',
'name' => $product->getName(),
'sku' => $product->getSku(),
'image' => $product->getImageUrl(),
'url' => $product->getProductUrl(),
'description' => trim(preg_replace('/\s+/', ' ', $this->stripTags($product->getShortDescription()))),
'offers' => array(
'@type' => 'Offer',
'availability' => $json['availability'],
'price' => number_format((float)$product->getFinalPrice(), 2, '.', ''),
'priceCurrency' => $currencyCode,
'category' => $json['category']
)
);// if reviews enabled - join it to $data array
if($review){
$data['aggregateRating'] = array(
'@type' => 'AggregateRating',
'bestRating' => '5',
'worstRating' => '0',
'ratingValue' => $json['ratingValue'],
'reviewCount' => $json['reviewCount']
);
$data['review'] = $reviewData;
}
// getting all attributes from "Attributes" section of or extension's config area...
$attributes = Mage::getStoreConfig('richsnippets/attributes');
// ... and putting them into $data array if they're not empty
foreach($attributes AS $key => $value){
if($value){
$data[$key] = $this->getAttributeValue($value);
}
}
// return $data table in JSON format
return '[' . json_encode($data) . ']';
}
return null;}
}
Die Kommentare im Code beschreiben seine einzelnen Teile, so brauchen wir nicht, den Code hier noch einmal zu beschreiben.
Zum Schluss „pushen” wir alles in die Vorlage:
<?xml version="1.0"?>
<layout>
<default></default>
<catalog_product_view>
<reference name="footer">
<block type="richsnippets/jsonld" name="richsnippets" after="-" template="richsnippets/index.phtml" />
</reference>
</catalog_product_view>
</layout>
<?php // check if module is enabled in our extension's backend configuration ?>
<?php if(Mage::getStoreConfig('richsnippets/general/enabled')){ ?>
<script type="application/ld+json">
<?php // echo our JSON array ?>
<?php echo $this->getStructuredData(); ?>
</script>
<?php } ?>
Und fertig! So haben wir unser Modul zu Ende geschrieben. Jetzt wäre es also ratsam, die Funktion auf Richtigkeit zu überprüfen.
Testing, testing, testing…
Leider haben weder Google, Bing noch Yahoo ein Tool zur Verfügung gestellt, mit dem die Vorschau von Suchergebnissen möglich wäre, und das die strukturellen Daten JSONLD bedienen würde, so müssen wir auf den Endeffekt so lange warten, bis die Bots unsere Produkte indizieren. Es gibt aber eine Möglichkeit, die Richtigkeit der von uns übersendeten Informationen zu kontrollieren. Dazu brauchen wir den Tag Tester in der E-Mail, von Google. Also das beliebige Produkt in unserem Shop öffnen, die volle Seitenquelle kopieren und unseren HTML in den Tester eingeben. Wenn alles richtig gemacht wurde, sollten wir ungefähr solch ein Ergebnis zu sehen bekommen:
In der aktuellen Version kann unser Modul die definierbaren Produkte sowie Gruppen nicht komplex bedienen. Es kann ausschließlich Informationen über das Angebot übergeben, das es auf der Seite bei deren Anzeige vortrifft. Ein blaues Hemd kann etwas teurer als das weiße Hemd sein. In diesem Fall sind wir aber nicht im Stande, dies direkt in der Suchmaschine zu zeigen. Vielleicht wage ich es in der Zukunft, diesen Zusatz zu erweitern. In der ersten Version versuchte ich aber, den Server zu beachten und möglichst wenig zu belasten.
Nachstehend Zusatz zum Downloaden.