Schick programmiert

Block außerhalb von Magento laden

In Magento ist alles, was im Shop zu sehen ist, als Block definiert.

Ich habe eine Möglichkeit gefunden einen Block auch auf einer anderen Seite zu laden, muss aber auf dem gleichen Server sein. Bisher hatte ich die zwei folgenden Anwendungsfälle:

  1. Block Random Products in einem WordPress-Auftritt anzeigen
  2. Eigene Seite für Statistiken nur anzeigen, wenn man auch im Backend angemeldet ist

Hierbei muss man zuerst entscheiden, welchen Store man in der externen Seite laden will. So lange man nur einen Store hat ist das alles recht einfach … Andernfalls muss man sich entscheiden, welchen Store man laden möchte.

Gehen wir zuerst das WordPress-Beispiel an:

Da dieser WordPress-Auftritt nur für einen Store genutzt wurde, fiel die Entscheidung leicht.

<?php
// Load the Magento-App so I can use its default-configuration values in here
require dirname(__FILE__) . '/app/Mage.php';
$app = Mage::app('shoes');

?>

Der Funktion app() bekommt in diesem Beispiel den Store-Code shoes mitgegeben. Magento lädt diesen Store mit allen Einstellungen. Wenn kein Code gesetzt wurde, wird der Standard-Store geladen. Der Standard-Store wird über die Website-Einstellungen und eine Store-Gruppe definiert.

Die Codes der Stores können in der Datenbanktabelle mage_core_stores eingesehen werden. Wenn jemand eine Stelle im Admin-Bereich kennt, wo man diesen Code einsehen kann, schreibt es bitte in das Kommentarfeld.

Nachdem der Magento-Core geladen ist, kann man mit folgendem Code einen Block laden:

<?php
if (!Mage::getStoreConfigFlag('advanced/modules_disable_output/Mage_Catalog'))
	echo Mage::getSingleton('core/layout')->createBlock('catalog/product_list_random')->setTemplate('catalog/product/list.phtml')->toHtml();
?>

Die IF-Bedingung habe ich darum gesetzt, weil man die Ausgabe von einem Modul im Backend deaktivieren kann. Diese Einstellung wird von der Funktion createBlock() nicht berücksichtigt.

Der zweite Anwendungsfall war auch nicht sehr schwierig.

Um zu Überprüfen, ob der Besucher im Backend angemeldet ist, kann folgender Code verwendet werden:

<?php

//get the admin session
Mage::getSingleton('core/session', array('name'=>'adminhtml'));

//verify if the user is logged in to the backend
if(Mage::getSingleton('admin/session')->isLoggedIn())
    echo "logged in";
else
    echo "not logged in";

?>

Danke Silvo für das Code-Beispiel: http://stackoverflow.com/questions/3342165/magento-how-to-check-if-admin-is-logged-in-within-a-module-controller

Selbstverständlich muss vor diesem Code bereits ein Store geladen sein.

Weiterführende Links:

http://fishpig.co.uk/magento-tutorials/accessing-static-blocks-in-magento
http://activecodeline.net/magento-init-process-bare-essentials


Magento Block Caching

Das Thema Caching ist bei Magento mit eines der Wichtigsten. Genau aus diesem Grund habe ich mich auch gewundert, als bei einem Shop der Cache für Block HTML deaktiviert war.

Der vorige Programmierer hatte dazu keine klare Aussage – also hab ich den Cache einfach aktiviert.

Problem: Der Link zum Warenkorb zeigt immer an, wie viele Produkte aktuell im Warenkorb liegen. Diese Anzahl wurde jetzt auch gecached. Somit hat der nächste Benutzer (der nach mir die Seite aufgerufen hat) gesehen, dass der Benutzer, der zuerst nach Leerung des Caches die Seite aufgerufen hat, 5 Produkte im Warenkorb hatte. Sorry für den komplizierten Satz..

Die Lösung habe ich in einer Serie von Blog-Einträgen für Weihnachten gefunden: http://www.webguys.de/magento/turchen-22-magento-block-caching/

Weiterführende Links zum Magento-Wiki:
http://www.magentocommerce.com/wiki/5_-_modules_and_development/block_cache_and_html_ouput
http://www.magentocommerce.com/wiki/modules_reference/english/mage_adminhtml/system_cache/index

Man kann über die XML-Konfiguration (wo der Block definiert wird) oder in der PHP-Klasse (die den eigentlichen Block dar stellt) verschiedene Einstellungen für den Cache treffen. Die folgenden Punkte werden auf der o.g. Seite beschrieben:

  1. Lifetime – Lebenszeit
  2. Cache Key
  3. Cache Tags

Zur lifetime des Caches ist auf der o.g. Seite dokumentiert, dass man einen Wert in Sekunden angeben kann, 0 oder false für „cache allways“ und null für „no cache“.

Diese Dokumentation kann ich nicht bestätigen. Ich arbeite mit Magento 1.4.0.1 und setze in der XML-Konfiguration die lifetime auf 0 und das caching ist deaktiviert.

Die anderen beiden Punkte möchte ich hier nicht ansprechen, da ich sie nicht getestet habe. Sie sind aber auch mit einigem Text auf der genannten Seite beschrieben.

Weiterhin möchte ich eine andere – nicht Problem – aber Stolperfalle in einem Beispiel erklären.

Nehmen wir an, ich habe den Block Footer und habe dort keine Cache-Informationen gesetzt. Laut der o.g. Website sollte dann der Cache für meinen Block deaktiviert sein – aber da hat vielleicht mein Vorgänger dran rum gespielt – bei mir ist der Cache dann aktiviert. Ich weiß nur nicht für welchen Zeitraum.

In diesen Block Footer stecke ich jetzt einen anderen Block rein und nenne den FooterLinks. Angenommen mein Footer hat eine lifetime von 300 Sekunden (automatisch – durch irgendeine Konfiguration) und mein FooterLinks hat eine lifetime von 0 Sekunden (sollte laut genannter Website „cache allways“ sein – aber bei mir komischerweise „no cache“).

Bei der ersten Anzeige wird der Cache aufgebaut. FooterLinks mit 0 Sekunden und Footer mit 300 Sekunden. Der nächste Aufruf wird zuerst den Block Footer laden, dessen Cache eine lifetime von 300 Sekunden hat und darum den Block FooterLinks für die 300 Sekunden mit gecached hat.

Daher meine Anweisung:

Beim Einstellen der lifetime bei einem Block immer darauf achten, dass auch alle Parent-Elemente eine mindestens gleich-niedrige Cache-lifetime haben.

Getestet mit Magento 1.4.0.1

Bitte schreibt‘ ein Kommentar, wenn sich das in eurer Magento-Version anders‘ verhält.

Zum Schluss ein Beispiel für meinen XML-Code, wo es darum geht den Link für zum Warenkorb nicht im Cache zu speichern. Zusammenkopiert aus 2 XML-Dateien um mal ein etwas komplexes Beispiel zu geben:

    <default>
		<reference name="root">
			<block type="page/html_header" name="header" as="header" template="page/html/header.phtml">
                <action method="setCacheLifetime"><num>0</num></action>

				<block type="page/template_links" name="top.links" as="topLinks">
                    <action method="setCacheLifetime"><num>0</num></action>
				</block>
            </block>
		</reference>
	</default>
	<default>
		<reference name="top.links">
			<block type="checkout/links" name="checkout_cart_link">
                <action method="setCacheLifetime"><num>0</num></action>
				<action method="addCartLink" />
				<action method="addCheckoutLink" />
			</block>
		</reference>
	</default>

Magento Admin – Link zum Produkt von der Kategorie-Bearbeitungsansicht

Heute bin ich über einen Link gestolpert (Ihr wisst ja … man such eigentlich nach etwas ganz anderem – findet dann aber so was) in dem beschrieben wird, wie man einen Link zum Produkt in die Kategoriebearbeitungsansicht einfügen kann. So etwas habe ich schon lange gesucht.

Wo der Link erscheint?
Ich gehe ins Backend von Magento, klicke unter „Katalog“ auf „Kategorien verwalten“ und wähle eine Kategorie aus. In dem Reiter „Kategorie Artikel“ bekommt man eine tabellarische Auflistung aller Produkte, die in der Kategorie liegen.

Mit dem folgendem Code kann man dieser Tabelle eine neue Spalte „Aktion“ hinzufügen und dort für jedes Produkt einen „Bearbeiten“-Link anzeigen.
Nach meiner Meinung sollte diese Änderung in jedes Magento-Projekt hinein.

Datei: /app/code/core/Mage/Adminhtml/Block/Catalog/Tab/Product.php
Funktion: _prepareColumns()
Code:

        $this->addColumn('action',
            array(
                'header'    => Mage::helper('catalog')->__('Action'),
                'width'     => '50px',
                'type'      => 'action',
                'getter'     => 'getId',
                'actions'   => array(
                    array(
                        'caption' => Mage::helper('catalog')->__('Edit'),
                        'url'     => array(
                            'base'=>'*/catalog_product/edit',
                            'params'=>array('store'=>$this->getRequest()->getParam('store'))
                        ),
                        'field'   => 'id'
                    )
                ),
                'filter'    => false,
                'sortable'  => false,
                'index'     => 'stores',
        ));

 

Meine Empfehlung zur Implementierung des Codes:

Kopiert‘ euch die Datei /app/code/core/Mage/Adminhtml/Block/Catalog/Tab/Product.php nach /app/code/local/Mage/Adminhtml/Block/Catalog/Tab/Product.php und ändert die Funktion dort ab.

Da die Spalte schön an das Ende der Tabelle passt, könnt ihr den Code direkt vor return parent::_prepareColumns(); einpflegen.

Dadurch haltet ihr euer System etwas sauber und könnt eure Änderungen beim nächsten Magento-Update behalten – selbst wenn ihr alle Core-Dateien überschreibt.

Wer Lust hat, wird hiermit dazu aufgefordert ein Magento-Plugin zu schreiben, was diese Spalte hinzufügt. Die Code-Nacharbeitung bei einem Magento-Updates wird dadurch weiter reduziert.

Vielen Dank an das Team von YARTAP MARKETING AND WEB DEVELOPMENT für das Schreiben dieses Artikels:
http://www.yartapmarketing.com/blog/magento-admin-link-to-product-from-category-page/


Reparatur von GoogleAnalytics in Magento 1.4.0.1

In einer Magento-Installation (Version 1.4.0.1) wurden im GoogleAnalytics-Account fast keine Bestellungen getrackt.

Die Seite /checkout/success/ selbst wurde um ein vielfaches öfter getrackt, als Bestellungen im ECommerce-Bereich von GoogleAnalytics zu finden waren.

Nachdem ich Google wieder etwas gequält habe, bekam ich einen Blog-Beitrag von inchoo.net zu Gesicht, in dem sie das gleiche Problem hatten. http://inchoo.net/ecommerce/magento/magento-1-4-0-1-google-analytics-fix/

Sie haben es gelöst, indem sie den JavaScript-Fehler behoben haben, der dadurch entstand, das die Variable _gaq nicht definiert war und als Array genutzt wurde. Anscheinend war dies aber nicht der einzige Fehler.

In den Release-News der Magento-Version 1.4.2.0 stehen Informationen von mehreren Fixes im Modul GoogleAnalytics … In diesem Zuge wurde direkt ein komplettes Refactoring des Modules vorgenommen. Nachdem ich diese Information gelesen hatte, habe ich einen SVN-Zugang zum Magento-Projekt gesucht und auch gefunden.

http://svn.magentocommerce.com/source/branches/1.6-trunk/app/code/core/Mage/GoogleAnalytics/

Statt dem 1.6-trunk kann man hier auch durch einfaches Ändern der URL auf den 1.4-trunk zugreifen. Dort habe ich mir das gesamte Modul GoogleAnalytics Datei für Datei heruntergeladen (sind ja nur 6) und direkt in einem Patch geprüft welche Daten sich in diesen Versionen geändert haben.

Nachdem ich mit einer Woche Arbeit die GA-Statistik des Unternehmens versaut habe (indem Google gar keine Bestellungen im ECommerce-Bereich getrackt hat) bekam ich doch eine recht gute Version heraus, die ich euch gerne zur Verfügung stellen möchte.

Hier ist der Auszug eines Diff meiner Änderungen um die aktuelle Version des 1.6-trunk zum Laufen zu bekommen. Sollte der 1.6-trunk nicht mehr funktionieren, sollte es auch nicht viel ändern wenn man die Daten aus dem 1.5-trunk nutzt.

In dem hier geschriebenen Diff habe ich zusätzlich eine Änderung eingespielt, die in diesem Post beschrieben wird (nur den ersten Punkt – falsche Reihenfolge der GA-Daten und dem Erstellen des Script-Tags): http://magentist.com/magento_help/magento-google-analytics-issues/

 

Index: trunk/brodering/app/code/core/Mage/GoogleAnalytics/Model/Observer.php
===================================================================
--- trunk/brodering/app/code/core/Mage/GoogleAnalytics/Model/Observer.php	(revision 34)
+++ trunk/brodering/app/code/core/Mage/GoogleAnalytics/Model/Observer.php	(revision 35)
@@ -58,11 +58,11 @@
     public function setGoogleAnalyticsOnOrderSuccessPageView(Varien_Event_Observer $observer)
     {
-        $orderIds = $observer->getEvent()->getOrderIds();
-        if (empty($orderIds) || !is_array($orderIds)) {
+        $quoteId = Mage::getSingleton('checkout/session')->getLastQuoteId();
+        if (!$quoteId) {
             return;
         }
         $block = Mage::app()->getFrontController()->getAction()->getLayout()->getBlock('google_analytics');
         if ($block) {
-            $block->setOrderIds($orderIds);
+            $block->setQuoteId($quoteId);
         }
     }
Index: trunk/brodering/app/code/core/Mage/GoogleAnalytics/Block/Ga.php
===================================================================
--- trunk/brodering/app/code/core/Mage/GoogleAnalytics/Block/Ga.php	(revision 34)
+++ trunk/brodering/app/code/core/Mage/GoogleAnalytics/Block/Ga.php	(revision 37)
@@ -105,11 +105,12 @@
     protected function _getOrdersTrackingCode()
     {
-        $orderIds = $this->getOrderIds();
-        if (empty($orderIds) || !is_array($orderIds)) {
-            return;
-        }
-        $collection = Mage::getResourceModel('sales/order_collection')
-            ->addFieldToFilter('entity_id', array('in' => $orderIds))
-        ;
+		$quoteId = $this->getQuoteId();
+		if (empty($quoteId)) {
+			return;
+		}
+		$collection = Mage::getResourceModel('sales/order_collection')
+			->addAttributeToFilter('quote_id', $quoteId)
+			->load()
+		;
         $result = array();
         foreach ($collection as $order) {
@@ -142,4 +143,7 @@
      * Render GA tracking scripts
      *
+     * @author Simon Schick- Done some fixes
+     * @see http://magentist.com/magento_help/magento-google-analytics-issues/
+     *
      * @return string
      */
@@ -154,4 +158,8 @@<script type="text/javascript">// <![CDATA[
 +    var _gaq = _gaq || []; +' . $this->_getPageTrackingCode($accountId) . '
+' . $this->_getOrdersTrackingCode() . '
+
     (function() {
         var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
@@ -159,8 +167,4 @@
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
     })();
-
-    var _gaq = _gaq || [];
-' . $this->_getPageTrackingCode($accountId) . '
-' . $this->_getOrdersTrackingCode() . '
// ]]></script>