Die Idee von n-n relations und MySQLs GROUP_CONCAT
Posted by Simon Schick | Filed under SQL
Ich bin einige Zeit mit dem Gedanken gegangen, wie man Daten effektiv laden kann, die in einer NN-Verknüpfung miteinander stehen.
Letztens bin ich dabei auf die Funktion GROUP_CONCAT() gestoßen: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
Nehmen wir ein Beispiel, wo wir ein System haben, in dem eine große Liste an Artikeln verwaltet wird in einer übersichtlichen Gruppe von Kategorien.
Vorher habe ich es so aufgebaut, dass ich bei jedem Zugriff auf die Artikel immer zwei Abfragen gesendet habe: eine um die Artikeldaten zu bekommen und eine um die zugehörigen Kategorien zu laden. Zusätzlich hatte ich eine Liste aller Kategorien im Speicher, da sie im weiteren Verlauf des Scripts immer wieder gebraucht wurden.
Warum nicht alle Kategorien am Anfang in den Speicher laden (vielleicht zusätzlich als Objekte serialisieren und in einem Speicher wie Memcache lagern) und mit nur einer Abfrage mehrere Artikel und deren Kategorien zu laden?
Ein SQL-Befehl zu dem gegebenen Beispiel würde ungefähr so aussehen:
Zuerst alle Kategorien laden:
SELECT * FROM category;
Dann kommt die Liste der Artikel mit Kategorie-Ids (als csv):
SELECT article.*, GROUP_CONCAT(article_has_category.category_id) FROM article LEFT JOIN article_has_category ON (article.id = article_has_category.article_id) GROUP BY article.id;
Der gesamte PHP-Code könnte dann so aussehen:
class Article {
private $_categoryIds = array();
public function __construct($row) {
$this->_categoryIds = explode(",", $row['category_ids']);
}
public function getCategories() {
$cat = array();
foreach($this->_categoryIds as $id)
$cat[] = CategoryRepository::getCategoryById($id);
return $cat;
}
}
class Category {}
class CategoryRepository {
private static $objects;
public static function load() {
$res = mysql_query("SELECT * FROM category");
while(($row = mysql_fetch_array($res))) {
self::$objects[$row['id']] = new Category($row);
}
}
public static function getCategoryById($id) {
return self::$objects[$id];
}
}
class ArticleRepository {
public static function getArticles() {
$objects = array();
$res = mysql_query("SELECT article.*, GROUP_CONCAT(article_has_category.category_id) AS 'category_ids' FROM article LEFT JOIN article_has_category ON (article.id = article_has_category.article_id) GROUP BY article.id");
while(($row = mysql_fetch_array($res))) {
$objects[] = new Article($row);
}
return $objects;
}
}
mysql_connect('localhost', 'root', 'root');
mysql_select_db('testdb');
CategoryRepository::load();
$articles = ArticleRepository::getArticles();
foreach($articles as $article)
$article->getCategories();
Wer das gleich ausprobieren will, kann diese Testdaten verwenden:
CREATE TABLE IF NOT EXISTS `article` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=3 ; INSERT INTO `article` (`id`, `title`) VALUES (1, 'Lorem Ipsum'), (2, 'Cooler Collins'); CREATE TABLE IF NOT EXISTS `article_has_category` ( `article_id` int(11) NOT NULL, `category_id` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; INSERT INTO `article_has_category` (`article_id`, `category_id`) VALUES (1, 1), (1, 3), (2, 3); CREATE TABLE IF NOT EXISTS `category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=4 ; INSERT INTO `category` (`id`, `title`) VALUES (1, 'Gruppe1'), (3, 'Gruppe2'); CREATE TABLE IF NOT EXISTS `friendships` ( `initiator_id` int(11) NOT NULL, `reciprocator_id` int(11) NOT NULL, UNIQUE KEY `initiator_id` (`initiator_id`,`reciprocator_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Diese Funktion hat nicht nur MySQL sondern auch PostgreSQL – nur hat sie da einen anderen Namen:
http://stackoverflow.com/questions/2560946/postgresql-group-concat-equivalent
Für MSSQL sieht es hier schwierig aus. Wenn sich die Situation noch nicht geändert hat muss ein Workarround geschrieben werden:
http://stackoverflow.com/questions/451415/simulating-group-concat-mysql-function-in-ms-sql-server-2005
Htaccess Manager in PHP
Posted by Simon Schick | Filed under PHP
Eines meiner Projekte teilt sich in mehrere Subdomains auf von denen jede über einen Passwortschutz gesichert ist.
Damit ich die Passwörter der Personen nicht alle selbst pflegen muss, habe ich über eine Möglichkeit nachgedacht ein Script zu schreiben, wo diese Benutzer selbst ihr Passwort nach belieben abändern können.
Da ich selbst noch etwas zwischen Apache und Nginx hinke habe ich diese Implementation so gestaltet, dass PHP die gesamte Anmeldung kontrolliert.
Die Applikation ist bisher recht einfach aufgebaut. Wenn sich ein Benutzer anmeldet (Benutzer und Passwort werden aus einer registrierten htaccess Datei geladen) bekommt er ein Formular in dem er sein Passwort ändern kann. Der Admin bekommt zusätzlich eine Liste aller registrierten Benutzer angezeigt.
Zukünftige Funktionen:
- Admin kann Benuzter hinzufügen
- Admin kann Benutzer löschen
- Admin kann Passwörter anderer Benutzer ändern
- SHA1 Passwörter unterstützen
Ein Problem ist auch bereits bekannt, welches aber auf einer Misskonfiguration des Apache2 beruht. Wenn ihr PHP über das FastCGI-Modul nutzt solltet ihr euch diesen Beitrag ansehen:
Symfony – nginx: Environment nach Cookie bestimmen
Posted by Simon Schick | Filed under Symfony2
Mir kam die Idee, ob es nicht möglich ist anhand eines Cookies zu entscheiden, ob Symfony nun in der Production- oder Development-Environment geladen werden soll.
Die Antwort ist: Ja!
Für nginx habe ich eine Möglichkeit gefunden und möchte sie euch hier zur Verfügung stellen:
server {
listen 80;
server_name *.sf2;
root /var/www/$host/web;
access_log /var/www/log/nginx.sf2.access.log;
error_log /var/www/log/nginx.sf2.error.log;
location / {
if (-f $request_filename) {
break;
}
set $app_index app_dev.php;
if ($http_cookie ~ "(; )?env=prod") {
set $app_index app.php;
}
index $app_index;
rewrite ^(.*)$ /$app_index last;
}
location ~ (app|app_dev).php {
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
}
}
Für jemand, der noch nicht so mit nginx vertraut ist empfehle ich die Dokumentation von nginx anzusehen.
Der oben beschriebene Code erstellt einen neuen vHost, der auf alle *.sf2 Domains reagiert. Dieser bekommt als Root-Pfad den web-Ordner aus dem Symfony-Projekt zugewiesen. Bei jedem Aufruf wird nun geprüft, ob der string HTTP_COOKIE den Wert env=prod enthält – mit anderen Worten – es wird geprüft ob der Cookie env den Wert prod enthält. Wenn ja, werden alle nicht gefundenen Resources nach app.php geleitet, sonst nach app_dev.php.
Über die zweite Location-Anweisung wird sicher gestellt, das einzig diese beiden Dateien (app.php und app_dev.php) an den PHP-Parser weitergeleitet werden, der lokal auf dem Port 9000 lauscht.
Diese Konfiguration wird aktuell auf meinem Testsystem eingesetzt und hat bisher noch keine Probleme gemacht. Wenn ihr wollt, könnt ihr das ganze auch umdrehen und auf eurem Production-Server einsetzen – dann würde ich aber noch die IP mit einbeziehen, damit nicht jeder auf die Development-Environment zugreifen kann.
Wenn jemand diese Lösung für andere Systeme umsetzen möchte, darf er das gerne in den Kommentaren oder in seinem eigenen Blog tun (dann hätt ich aber gerne nen Link in meinen Kommentaren).
Magento Fehler beim Öffnen des XML-Export
Posted by Simon Schick | Filed under Magento
Ein Fehler, der beim Öffnen von bestimmten Magento-XML-Exporten auftritt, hat mich heute wieder beschäftigt.
Zuerst mal ein Beispiel, mit dem man das Problem nachvollziehen kann:
<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?> <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:x2="http://schemas.microsoft.com/office/excel/2003/xml" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"> <Worksheet ss:Name="Sheet 1"> <Table> <Row> <Cell> <Data ss:Type="String">sku</Data> </Cell> <Cell> <Data ss:Type="String">name</Data> </Cell> </Row> <Row> <Cell> <Data ss:Type="Number">40</Data> </Cell> <Cell> <Data ss:Type="String">Sample Product 40</Data> </Cell> </Row> <Row> <Cell> <Data ss:Type="Number"> 4021</Data> </Cell> <Cell> <Data ss:Type="String">Sample Product 4021</Data> </Cell> </Row> </Table> </Worksheet> </Workbook>
In diesem Beispiel sieht man, dass der vorletzte Zellen-Inhalt als Nummer definiert ist, aber ein Leerzeichen enthält. Dies löst beim XML-Parser von Excel einen Fehler aus. Ist auch auf eine Weise ganz gut so – aber ich frag mich mehr warum das als Zahl definiert wurde …
Nach etwas Suchen kam ich auch bald dahinter, dass Magento diese Datei mit Hilfe des ConvertParsers Varien_Convert_Parser_Xml_Excel erzeugt.
...
foreach ($row as $value) {
$this->_xmlElement->row = htmlspecialchars($value);
$value = str_replace($xmlHeader, '', $this->_xmlElement->asXML());
$value = preg_replace($xmlRegexp, '\\1', $value);
$dataType = "String";
if (is_numeric($value)) {
$dataType = "Number";
}
$value = str_replace("\r\n", ' ', $value);
$value = str_replace("\r", ' ', $value);
$value = str_replace("\n", ' ', $value);
$xmlData[] = '<Cell><Data ss:Type="'.$dataType.'">'.$value.'</Data></Cell>';
}
...
Nach der Definition auf php.net ist die ausgegebene XML-Datei auch vollkommen in Ordnung. Siehe: http://php.net/manual/de/function.is-numeric.php#16310
Nur erwartet Excel in der Zelle wirklich nur eine Nummer, wenn sie als Number definiert wurde.
Meine aktuelle Lösung besteht darin, dass ich die Zeile $dataType = “Number”; schlicht und ergreifend auskommentiere. Da ich nicht weiß, wie man diese Klasse überladen kann, hab ich den Core an dieser Stelle mal wieder abgeändert.
Eine bessere Lösung lässt sich aber vielleicht aus diesem Post erarbeiten: http://www.php.net/manual/de/function.is-int.php#95658
Bei diesem Post bin ich nur nicht damit einverstanden, dass eine Zahl wie 01233 auch als Number definert wird, da Excel die vorgestellte Null entfernen wird.
Dieses Problem habe ich bereits als Bug gepostet. Mal sehen wie lange diese Änderung braucht …
http://www.magentocommerce.com/bug-tracking/issue?issue=12209
Block außerhalb von Magento laden
Posted by Simon Schick | Filed under Magento
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:
- Block Random Products in einem WordPress-Auftritt anzeigen
- 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
Posted by Simon Schick | Filed under Magento
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:
- Lifetime – Lebenszeit
- Cache Key
- 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
Posted by Simon Schick | Filed under Magento
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
Posted by Simon Schick | Filed under Magento
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>
