Wo hat der neue Feuerkäfer die Kekse versteckt?

Nach dem FireBug update wird der eine oder andere plözlich den Cookie-Reiter vermissen. Dazu muss in den Optionen der Reiter Speicher aktiviert werden:

In meinem Fall war bereits ein solcher Reiter zu sehen, nach der aktivierung gibt es zwei gleichnamige Reiter. Die Cookies sind in dem rechten versteckt:

Empfehlung: Guter Tee passt immer gut zu Keksen 🙂

 

CSS-Style auf einer Produktseite einbinden

Möchte man eine Produktseite etwas umgestalten, lässt sich CSS über das Layout-Update in den Kopfbereich einfügen. Ohne eine zusätzliche CSS-Datei herunterladen zu müssen, können wir direkt im Layout-XML unsere CSS-Regeln definieren:

<reference name="head">
  <block type="core/text" name="style">
    <action method="setText">
      <text><![CDATA[<style>h1 {color:red}</style>]]></text>
    </action>
  </block>
</reference>

 

Weiterlesen:

Update auf Magento 1.9.2.3 oder SUPEE-7405: Bestelldetails laden nicht

Fehlerbeschreibung

Nach Update von Magento auf 1.9.2.3 oder Installation des Sicherheitspatches SUPEE-7405 wird die Bestellung in Detailansicht nicht geladen. Man sieht im Backend die folgende Ausgabe:

bestelldetails-magento-500-server-error

Mit FireBug lässt sich außerdem Server-Fehler 500 nachvollziehen.

Fehlerursache

Seit PHP 5.4 wurde ein neuer Array-Syntax eingeführt. Wird PHP Version unter 5.4 verwendet, kommt es zu diesem Fehler. Seit PHP 5.4 kann der Code $a = array(1, 2, 3) alternativ kürzer gefasst werden: $a = [1, 2, 3].

Fehlerbehebung

Um sich das Update von PHP  zu sparen, kann in Mage_Adminhtml_Helper_Sales (Datei: app/code/core/Mage/Adminhtml/Helper/Sales.php) in Zeile 124 die folgende Zeile:

$links = [];

durch die folgende ersetzt werden:

$links = array();

Weiterlesen:

Installation von Magento 2.0: Erster Eindruck

Seit dem 17.11. ist Magento 2.0 in der finalen Version raus. Die Kommunikation im Downloadbereich ist deutlich: Magento 1.x ist im Tab „Release Archive“ zu finden:

magento1-archive

Der Installationsassistent ist freundlich aber deutlich: Magento 2 erfordert mindestens PHP 5.5 und MySQL 5.6, sodass viele Testumgebungen in den sich Magento 1.x noch funktionierte ohne Update unbrauchbar sind.

Der aktuelle XAMPP erfüllt die Anforderungen von Magento 2. Auch die PHP-Extensions (curl, dom, iconv, mcrypt, spl, xsl, intl, mbstring, ctype, hash, openssl, simplexml, xmlwriter, gd) sind alle enthalten.

magento2-system-requirementsPHP-Extensions intl muss nur noch in der php.ini aktiviert werden, im Verzeichnis ext ist die Datei php_intl.dll bereits enthalten. Nach dem Setzen von always_populate_raw_post_data = -1 in der php.ini sind alle Kriterien erfüllt:

magento2-ready-to-install

Da wir in Zukunft Magento auch unterwegs vom Handy aus installieren wollen, ist schon der Installationsassistent responsive:

magento2-ready-to-install-responsiveIm aktuellen XAMPP wird übrigens MariaDB 10.1.8 statt MySQL 5.6 mitgeliefert. MariaDB ist der binäre Ersatz für MySQL, d. h. alle binäre Dateien von MariaDB heißen mit denen von MySQL identisch, sodass alle Werkzeuge und Skripte für MySQL weiterhin funktioneren werden. Die MySQL Workbench würdigt MariaDB mit der folgenden Meldung, funktioniert aber trotzdem (vermutlich liegt es nur an der Versionsnummer 10.1.8 statt 5.6, die im Protokoll übermittelt wird):

xampp-maria-db-workbench

Der Installationsassistent von Magento 2 schlägt von sich aus eine kryptische URL für den Administrationsbereich vor:

magento2-admin-path

Unsere Zeitzone heißt anders, und man kann vorab die Ausgabe einzelner Module deaktivieren:

magento2-installation-zeitzone

Zusätzlich zu der kryptische Admin-URL wird noch auf ein schwaches Passwort hingewiesen:

magento-passwort-staerke

Die Datenbank wird nun nicht mehr im Hintergrund aufgebaut, sondern mit einer Fortschrittsanzeige und der Option es im Fehlerfall erneut zu versuchen:

magento2-db-installation-fortschritt-fehler

In diesem Fall ist der Speicher des Laufewerks vollgelaufen, auf dem die XAMPP-Testumgebung gerade läuft. Nach Speicherfreigabe und dem Klick auf Try Again wird der Fortschrittsbalken mit einer Animation zurückgespullt 🙂

magento2-installation-erfolgreich

So sieht das neue Login-Formular aus:

admin-loginUnd so sieht es im Backend aus:

magento2-backend

FireBug zeigt, dass diverse Schriftarten für die Menü-Icons nicht geladen wurden:

magento2-schriftartenDer Ordner pub/static/adminhtml/Magento/backend/en_US/fonts/opensans/bold ist tatsächlich leer. Magento 2 kopiert statische Dateien in öffentlich zugängliche Ordner bei Bedarf. In diesem Fall hat diese Logik anscheinend versagt, und wir finden sicherlich später warum.

Erklärung des Google Doodle zum 200. Geburtstag von George Boole

google-doodleBool’sche Algebra ist in der Programmierung nicht mehr wegzudenken. Heute feiert Google den 200. Geburtstag von George Boole, der die Bool’sche Algebra erfunden hat.

Während im Alltag uns die mathematischen Operationen wie Addition und Multiplikation eher begegnen, sind boolsche Operationen in der Programmierwelt der Alltag. Google demonstriert die Rechenergebnisse von vier Operatoren: AND, OR, XOR, NOT. Ähnlich wie 2 +3 = 5 ist, ist 1 OR 0 = 1. Bool’sche Operatoren rechnen mit binären Zahlen 1 (wahr, engl. true) und 0 (falsch, engl. false). Das Ergebnis einer Bool’schen Operation ist ebenfalls eine binäre Zahl.

Der Buchstabe g im Logo zeigt die Eingabe, die bestimmt, welche anderen Buchstaben leuchten (d. h. mit Farbe gefüllt werden). Ein leuchtender Buchstabe entspricht der binären 1 oder dem logischen Wert Wahr, während ein grauer Buchstabe der 0 entspricht, oder dem logischen Wert Falsch. Wird der x bzw. y eingeblendet, so ist x bzw. y wahr, d. h. gleich 1. Wird x bzw. y ausgeblendet, so ist x bzw. y falsch oder gleich 0. Die Animation des Logos wechselt die vier Kombinationen von x und y ab und zeigt in den anderen Buchstaben die Ergebnisse des jeweiligen Operators.

Intuitive Erklärung der Operatoren:

  • AND: Beides muss wahr sein, x und y müssen beide wahr sein.
  • OR: Mindestens eins von beiden x oder y muss wahr sein.
  • XOR: Genau eins x oder y muss wahr sein, aber nicht beide x und y.
  • NOT: Der umgekehrte Wert, die Negation, nicht wahr ist falsch, nicht 0 ist 1.

Die nachfolgende Wahrheitstabelle fasst alle vier Kombinationen zusammen:

G o o g l e
AND XOR OR x y NOT y NOT x
0 0 0 0 0 1 1
0 1 1 0 1 0 1
0 1 1 1 0 1 0
1 0 1 1 1 0 0

Auf der Seite mit Suchergebnissen ist die Animation nicht mehr zu sehen. Dort setzt Google das Thema mit dem &-Zeichen vor dem Buchstaben g fort:

google-mini-bool

Weiterlesen:

Static Blocks Everywhere: Keine Ausgabe nach Installation von SUPEE-6788

Static Blocks Everywhere auf Magento Connect

Fehlerbeschreibung

Kurz nach der Veröffentlichung des Magento-Patch SUPEE-6788 und Magento-Update haben wir mehrere Anfragen bekommen, dass die Ausgabe für statische Blöcke in Zusammenhang mit der Erweiterung Static Blocks Everywhere nicht mehr funktioniert.

Ursache

Das Problem hängt mit der Verfeinerung des Rechtesystems in Magento zusammen.

Lösung

Zur Behebung des Problems muss unter System > Berechtigungen > Blocks ein neuer Block angelegt werden:

system-berechtigungen-blocks

Der Blockname ist cms/block, Is Allowed wird auf Yes/Ja gesetzt:

cms-block-berechtigungNach dem Speichern des neuen Blocks sieht die Liste der Block-Berechtigungen wie folgt aus:

magento-berechtigungen-blocks

Siehe auch:

MySQL: Job failed to start, mysqld: Can’t create test file user.lower-test

Fehlerbeschreibung

Nach dem Verschieben des MySQL-Datenverzeichnisses (Parameter datadir in /etc/mysql/my.cnf) startet der MySQL-Server nicht mehr. Als Fehler wird Job failed to start gemeldet:

root@ubuntu:~# /etc/init.d/mysql restart
Rather than invoking init scripts through /etc/init.d, use the service(8)
utility, e.g. service mysql restart

Since the script you are attempting to invoke has been converted to an
Upstart job, you may also use the stop(8) and then start(8) utilities,
e.g. stop mysql ; start mysql. The restart(8) utility is also available.
start: Job failed to start

Der MySQL-Server-Start direkt aus dem bin-Verzeichnis endet mit dem Fehler Can’t create test file /…./user.lower-test:

root@ubuntu:/bin# mysqld
150831  0:40:58 [Warning] Can't create test file /..../user.lower-test
150831  0:40:58 [Warning] Can't create test file /..../user.lower-test

Mögliche Ursache

Bei Ubuntu ist AppArmor eine häufige Ursache. Diese Sicherheitssoftware enthält ein Profil für MySQL, in dem u. a. das Datenverzeichnis von MySQL festgelegt ist. AppArmor blockiert den Zugriff von MySQL, weil das neue Datenverzeichnis nicht bekannt ist.

Lösung

Durch Eintrag des neuen Daten-Verzeichnisses in /etc/apparmor.d/usr.sbin.mysqld kann der Fehler behoben werden:

vi /etc/apparmor.d/usr.sbin.mysqld

...
  # /var/lib/mysql/ r,
  # /var/lib/mysql/** rwk,
  /neues-datenverzeichnis/mysql/ r,
  /neues-datenverzeichnis/mysql/** rwk,
...

Nach dem Neustart von AppArmor sollte auch MySQL-Server nun starten:

/etc/init.d/apparmor restart
/etc/init.d/mysql restart

Magento-Marktanteil in 2015

Entwicklung seit 2012

Update von der Meet Magento 2015

Auf der diesjährigen Meet Magento hat uns Thomas Goletz (Vorstand der Netresearch App Factory AG) gab es einige Updates zu den Statistiken rund um die Magento-Infrastruktur:

  • Mehr als 240.000 Installationen
  • Mehr als 7.500 bekannte Erweiterungen
  • Über 7 Jahre erfolgreiches Magento 1.x

Marktanteile von Magento nach Wappalyzer

Nach Wappalyzer (ein Browser-Plug-In, der die auf der Webseite verwendete Software basierend auf dem Quellcode u. a. Merkmalen erkennt) ist der Marktanteil von Magento auf 19% weiter gesunken:

Absolute Zahlen der Top 10 Online-Shop-Systeme:

Die Spalte „Detections“ zeigt indirekt die Besucherzahlen und damit die Popularität einzelner Shops, die das jeweilige System verwenden.

Zum Vergleich: 2012 hat Wappalyzer bei 25% aller untersuchten Online-Shops Magento erkannt. 2014 waren es 22%, und heute noch 19%.

Nach dieser Quelle ist WooCommerce (WordPress-basierte Shops) der deutliche Gewinner mit einem Anstieg von 15% im Vergleich zum Vorjahr. Ob WooCommerce eine niedrige Einstiegshürde bietet und sich deshalb gut verbreitet oder ob Wappalyzer seine Erkennungsalgoritmen verbessert und die Statistik damit verzerrt, hat kann man nur vermuten.

Magento-Marktanteile nach Google Trends, Search Insights

Das Suchvolumen weltweit zeigt Magento als klaren Sieger mit stabiler Nachfrage:

In Deutschland gewinnt Magento mit deutlichem Abstand:

Während in den USA Shopify Magento überholt hat:

Up-Selling-Produkte werden nicht gespeichert

Fehlerbeschreibung

Nach der Auwahl von Up-Sell-Produkten im Backend wird nach dem Speichern des Hauptprodukts die Liste zurückgesetzt.

Fehlerursache

Der Fehler tritt auf, wenn die Erweiterung BL_CustomGrid (Enhanced Admin Grid auf Magento Connect und GitHub).

Fehlerbehebung

Der Fehler kann dadurch behoben werden, dass die Erweiterung speziell für die Tabelle der Up-Sell-Produkte deaktiviert wird. Die Erweiterung stellt dazu selbst die entsprechende Mittel zur Verfügung.

In Grid Anpassung > Grid Infos zur Up-Sell-Tabelle kann der Blocktyp herausgefunden werden:

In System > Konfiguration > Enhanced Admin Grids: Konfiguration Basis wird der Blocktyp adminhtml/catalog_product_edit_tab_upsell als Ausnahme hinzugefügt:

Danach kann zwar die Tabelle für Up-Sell-Produkte nicht mehr angepasst werden, doch das Speichern der Auwahl funktioniert.

Magento 1.9.1 sendet keine E-Mails

Seit Magento 1.9.1 wurden bei den Transaktions-E-Mails zwei wichtige Änderungen eingeführt: Responsive E-Mail-Vorlagen und E-Mail-Warteschlange (E-Mail-Queue). Mit dem ersten Shop auf Basis von Magento 1.9.1, wird man sich zunächst wundern, dass die Bestellbestätigung nicht sofort oder gar nicht nach Abschluss der Bestellung versendet wird, zumindest bis der Cron-Skript von Magento aufgerufen wurde.

Hinweis: Ein weiterer Grund, dass eine E-Mail nicht versendet wird könnte ein fehlendes E-Mail-Template oder ein Fehler im verwendeten Template sein.

Die E-Mail-Queue beschleundigt den Bestellvorgang, da die Bestellbestätigung im separaten Prozess versendet wird, und nicht wie in Vorgängerversionen, als Teilvorgang der Aufnahme einer neuen Bestellung. Dies ist Performance Optimierung, die sich besonders für Shops mit vielen Bestellabschlüssen lohnt. Mit der Magento-Erweiterung AOE_Scheduler lässt sich der Magento-interne Cron-Job der E-Mail-Queue gut veranschaulichen:

Die letzte Version dieser Erweiterung auf Magento Connect ist 0.3.2 und offiziell bis Magento 1.7 freigegeben, funktioniert jedoch mit Magento 1.9.1 ebenfalls einwandfrei. Die aktuelle Version 0.4.3 kann auf GitHub heruntergeladen werden.

Die Vorschaufunktion für E-Mail-Vorlagen ist in Magento ohnehin unbrauchbar, da sie die Vorschau ohne Kunden- oder Beispieldaten anzeigt, sodass das Endergebnis erst mit einer Testbestellung kontrolliert werden kann:

Bei der Einrichtung von E-Mail-Templates in einem neuen Shop wird die E-Mail-Queue die Arbeit allerdings erschweren, da die Email nicht sofort nach Abschluss einer Testbestellung ankommt, sodass bei der Kontrolle einer jeden Anpassung neben dem Aufwand einer Testbestellung noch eine zusätzliche Wartezeit entsteht.

Eine Abhilfe hierbei schafft die Magento-Erweiterung Modulwerft_EmailManager, die eine sofortige Vorschau bzw. Anzeige einer jeden von Magento vorbereiteten oder sofort versendeten E-Mail direkt im Backend ermöglicht.

Im Kommentarverlauf einer Bestellung kann die Bestellbestätigung, die an den Kunden versendet wurde oder wird, sofort augerufen werden:

An der Anzeige Versand ausstehend ist sofort erkennbar, dass die E-Mail noch nicht an den Kunden versendet wurde, sondern sich in der E-Mail-Warteschlange befindet. Der Klick auf den gelb hinterlegten Bereich zeigt die vorbereitete E-Mail so wie sie beim Kunden ankommen wird:

Neben der praktischen Anzeigefunktion der noch nicht versendeten E-Mails in Magento 1.9.1 überwacht und speichert der E-Mail-Manager alle E-Mails, die von Magento generiert und versendet wurden oder werden.

Dem Kundendienst wird diese Erweiterung helfen schnell nachzuvollziehen, welche E-Mails an den Kunden versendet wurden. Bei registrierten Kunden können die E-Mails in Kundendetails angezeigt und absteigend nach der Zeit sortiert werden:

In System > E-Mail-Log bekommt man die Übersicht über alle von Magento versendeten E-Mails und kann sie nach Typ (z. B. Bestellbestätigung, Rechnung, Lieferschein) Empfänger, Betreff und einigen weiteren Kriterien filtern. Nach dem Empfänger gefiltert können auch für Gastkunden alle versendeten E-Mails gefunden werden.

DHL-Label-Drucker und Etiketten-Format

DHL-Versand-Etiketten können entweder mit einem beliebigen Drucker gedruckt werden, der auch Papier im A5-Format bedrucken kann, oder mit einem Thermodrucker.

Etiketten-Drucker für DHL-Intraship

Von DHL wird der B-EV4T-G von Toshiba TEC empfohlen (vollständige Modellbezeichnung: B-EV4T-GS14-QM-R):

Der Anschaffungspreis beträgt inkl. MwSt. ca. 250,- EUR. Die passenden Thermo-Etiketten werden von DHL dem Kunden kostenfrei zugeschickt.

Der Drucker kann sowohl mit einer Etikettenrolle als auch mit gestapelten Etiketten verwendet werden, wie sie von DHL geliefert werden. Die Etiketten bleiben dann außerhalb des Druckers im Karton liegen und werden durch den hinteren Schlitz eingezogen. Da die Thermo-Etiketten von DHL die schwarze Druckfarbe bereits im Papier enthalten, wird das teuere Thermotransfer-Farbband nicht benötigt.

Der Vollständigkeit halber soll noch der große und ca. 700,- € (inkl. MwSt.) teuere Drucker B-EX4D2 von Toshiba erwähnt werden, der von der DHL bei 10.000 Sendungen pro Tag empfohlen wird. Der „kleine“ B-EV4T-G wird für viele Shop-Betreiber schnell genug sein: Für den Druck eines Etiketts (Format: s. u.) braucht er nur 2 Sekunden!

Installation des B-EV4T-G

Obwohl der Drucker einen Netzwerk-Anschluss hat und offiziell das Drucken über LAN unterstützen soll, haben wir es nicht geschafft ihn mit einem Windows-Rechner über LAN anzusprechen und betreiben ihn deshalb über USB.

In der eigenen Installationsanleitung schlägt DHL einen abgewandelten Druckertreiber vor: http://www.toshibatec-eu.de/IPD-PUBLIC/EasyLog/Treiber.zip

Dieser funktioniert einwandfrei, genau wie der offizielle Drucker-Treiber von Toshiba, jedoch wird das richtige DHL-Etikettenformat bereits vorinstalliert:

Die Testseite kann in den Druckereinstellungen gedruckt werden:

Gleich nach der Installation, ohne weitere Einstellungen, müsste der Drucker die Testseite erfolgreich ausgeben und dabei genau auf der Perforationslinie des nächsten Etiketts anhalten.

DHL-Etiketten-Format und DHL-Intraship in Magento

Wenn der offizielle Treiber von Toshiba bereits installiert wurde, kann das DHL-Etikettenformat (101,6 mm x 199,0 mm) manuell eingegeben werden:

In den Format-Einstellungen der Magento-Erweiterung DHL Intraship (die o. W. direkt mit dem neuen „DHL Versenden“ funktioniert) wird das passende Etiketten-Format nicht angeboten.

Für die Ränder können negative Abstände angegeben werden, um das perfekte Druckergebnis zu erzielen:

  • Papierformat der Etiketten: A5
  • Linker Rand: -53 mm
  • Oberer Rand: -49 mm

Eingaben der Abstände ohne Einheit in System > Konfiguration > Verkäufe: DHL > Etiketten-Einstellungen:

Das Ergebnis als PDF-Ausgabe liefert ein im linken oberen Rand des A5-Formats positionierten DHL-Etikett, der dann perfekt mittig aus dem Thermodrucker (B-EV4T-G) kommt:

SQL-Fehler in Magento debuggen: Vollständige SQL-Abfrage im Stack Trace anzeigen

Bei einem SQL-Fehler kann der Stack Trace von Magento in der Fehlerausgabe wie folgt aussehen:

#0 lib/Zend/Db/Statement/Pdo.php(228): PDOStatement->execute(Array)
#1 lib/Varien/Db/Statement/Pdo/Mysql.php(110): Zend_Db_Statement_Pdo->_execute(Array)
#2 lib/Zend/Db/Statement.php(300): Varien_Db_Statement_Pdo_Mysql->_execute(Array)
#3 lib/Zend/Db/Adapter/Abstract.php(479): Zend_Db_Statement->execute(Array)
#4 lib/Zend/Db/Adapter/Pdo/Abstract.php(238): Zend_Db_Adapter_Abstract->query('SELECT `a`.* FR...', Array)
#5 lib/Varien/Db/Adapter/Pdo/Mysql.php(419): Zend_Db_Adapter_Pdo_Abstract->query('SELECT `a`.* FR...', Array)
#6 lib/Zend/Db/Adapter/Abstract.php(825): Varien_Db_Adapter_Pdo_Mysql->query(Object(Varien_Db_Select), Array)

Die Information ist begrenzt hilfreich, da die Abfrage „SELECT `a`.* FR…“, die zu dem Fehler geführt hat, nicht ausgeschrieben wird.

Um die fehlerhafte Abfrage analysieren zu können, kann der Debug-Modus aktiviert werden, in dem die Variable $_debug in Varien_Db_Adapter_Pdo_Mysql auf true gesetzt wird. Die SQL-Abfrage wird dann in $_debugFile = ‚var/debug/pdo_mysql.log‘ gespeichert und kann weiter analysiert werden.

Rechtskonforme SEPA-Lastschrift für Magento mit Mandat-Verwaltung und SEPA-XML-Export (ISO 20022)

Seit heute steht die neue Version 2.0 der SEPA-Lastschrift-Erweiterung für Magento zur Verfügung:

Die neue Version erfüllt alle rechtlichen Vorschriften, die vom Gesetzgeber gefordert werden. Diese Anforderungen haben wir weiter unten für Sie zusammengefasst. Für die neue Version der Erweiterung stellen wir eine ausführliche Dokumentation und eine neue Live-Demo bereit.

Vielen Dank an alle Kunden und Bankmitarbeiter, die uns Verbesserungsvorschläge und rechtliche Hinweise zugeschickt haben und uns so bei der Verbesserung der SEPA-Lastschrift für Magento unterstützt haben!

Zusammenfassung der neuen Regeln für SEPA-Lastschrift

Im Zuge der Einführung der neuen SEPA-Lastschrift und der Abschaffung der nationalen Lastschrift-Verfahren, wie beispielsweise der deutschen Lastschrift, hat es Regeländerungen gegeben. Diese Regeländerungen sollten seitens des Online-Händlers unbedingt beachtet werden, da dieser sonst Gefahr läuft Lastschriften nicht regelkonform einzuziehen und damit die Frist für die Rückholung des Lastschriftbetrages seitens des Kunden zu verlängern.

Die nachfolgend aufgeführten Änderungen sind mit der Einführung der SEPA-Lastschrift in Kraft getreten. Die neue Version unserer SEPA-Lastschrift-Erweiterung für Magento unterstützt und automatisiert diese neuen Regelungen:

  • IBAN und BIC Angabe
    Die Erweiterung prüft die eingegebene IBAN und BIC auf Fehler und zeigt diese an. Die Fehlerquote bei der Eingabe des langen IBAN-Codes wird dadurch minimiert. Für bestimmte Länder kann BIC als optionale Eingabe eingestellt werden.
  • Verwaltung von SEPA-Lastschriftmandaten
    Zusätzlich zum Versenden des erteilten SEPA-Lastschriftmandats per Email werden alle Daten des Mandates und das Dokument in der Shop-Datenbank gespeichert. Registrierte Shop-Kunden können Lastschriftmandate für wiederkehrende Zahlung erteilen und sie bei nächster Bestellung nutzen ohne die Bankverbindung erneut einzugeben. Das Mandat verfällt automatisch 36 Monate nach Nichtnutzung.
  • Rechtskonforme Vorlagen für SEPA-Lastschriftmandate
    Mit der Erweiterung sind sorgfältig recherchierte Vorlagen nach Vorgaben des European Payment Council (EPC) und der Deutschen Kreditwirtschaft (DK) in deutscher und englischer Sprachen enthalten: SEPA-Lastschriftmandat für einmalige und wiederkehrende Zahlung und Vorabinformation. Auf den vom Schuldner abweichenden Kontoinhaber wird gesondern hingewiesen.
  • Vorabinformation (Pre-Notification)
    Die Erweiterung bietet die Möglichkeit den Hinweis auf Verkürzung der Frist für Vorabinformation im Checkout anzuzeigen und die Vorabinformation in der Bestellbestätigung anzuzeigen.
  • Automatischer SEPA-XML-Export nach ISO 20022
    Neben dem manuellen Export im Backend ist automatischer Export von Zahlungen im standardisierten SEPA-XML-Format möglich.

SEPA-Lastschrift für Magento auf Magento Connect
Wir laden Sie ein, die neuen Funktionen in unserer Live-Demo auszuprobieren!

WSDLSOAP-ERROR: Parsing WSDL: Couldn’t load from … : failed to load external entity …

Fehlerbeschreibung

$client = new SoapClient('http://beispiel.de/index.php/api/v2_soap/?wsdl');
$sessionId = $client->login('benutzer', 'geheim');

Beim Versuch eine API-Verbindung mit Magento herzustellen, wird von SoapClient der folgende Fehler gemeldet:

WSDLSOAP-ERROR: Parsing WSDL: Couldn't load from ... : failed to load external entity ...

Mögliche Ursache

Der Server kann seinen eigenen Namen nicht auflösen. Als Bestätigung dieser Ursache, kann der Apache-Server neu gestartet werden:

/etc/init.d/apache2 restart

Wird beim (erfolgreichen) Start von Apache die nachfolgende Warnung ausgegeben, liegt die o. g. Ursache vor:

Could not reliably determine the server's fully qualified domain name

Lösung

An Stelle der Domain kann in der API-URL die IP des Servers verwendet werden:

$client = new SoapClient('http://123.45.67.89/index.php/api/v2_soap/?wsdl');
$sessionId = $client->login('benutzer', 'geheim');

Die bessere und sauberere Lösung wäre der Eintrag der Domain in der hosts-Datei auf dem Server (die Datei befindet sich unter /etc/hosts):

123.45.67.89      beispiel.de

Anschließend sollte Apache neu gestartet werden.

Weiterlesen:

SoapClient: looks like we got no XML document

Fehlerbeschreibung

Beim Versuch sich über Magento-API einzuloggen, wird folgender Fehler gemeldet:

SoapClient: looks like we got no XML document

Fehleranalyse

Die Fehlermeldung ist viel zu allgemein. Deshalb sollte die Serverantwort genauer analysiert werden, die angeblich kein (gültiges) XML-Dokument ist. Um die empfangenen Daten analyseren zu können, wird die Abfragemethode von SoapClient überschrieben:

class SoapClientDebug extends \SoapClient{

    public function __doRequest($request, $location, $action, $version = SOAP_1_1, $one_way = 0){

        $xml = explode("\r\n", parent::__doRequest($request, $location, $action, $version, $one_way = 0));

        print_r($xml);
        print '<br/><br/>'.str_repeat('#', 100).'<br/><br/>';

        if(isset($xml[5])) return $xml[5];
        return '';
    }

}

Die API-Kommunikation läuft nun über die überschriebene Klasse:

// $client = new SoapClient('http://beispiel.de/index.php/api/v2_soap/?wsdl');
$client = new SoapClientDebug('http://beispiel.de/index.php/api/v2_soap/?wsdl');

Mögliche Ursachen und Lösungen

In unserem Fall hat der Server in mehreren Fällen tatsächlich kein XML-Dokument empfangen. Die Antwort war entweder eine Fehermeldung vom Varnish-Cache-Server, oder eine Weiterleitung aufgrund einer Regel in der .htaccess. Im ersten Fall konnte der Fehler durch Abschaltung des Varnish weiter analysiert werden. Im zweiten Fall wurde die Regel in der .htaccess verbessert.

WSDLSOAP-ERROR: Parsing Schema: can’t import schema from ‚http://schemas.xmlsoap.org/soap/encoding/‘

Fehlerbeschreibung

Beim Verbindungsaufbau mit Magento-API wird der nachfolgende Fehler gemeldet:

WSDLSOAP-ERROR: Parsing Schema: can't import schema from 'http://schemas.xmlsoap.org/soap/encoding/'

Mögliche Ursache

Der Aufruf der o. g. URL findet serverseitig statt. Vergewissern Sie sich, dass der Server die URL zugreifen kann:

wget http://schemas.xmlsoap.org/soap/encoding/

Wenn die Datei auf dem Server nicht heruntergeladen werden konnte, ist sie für den Server tatsächlich nicht erreichbar. Ausnahmsweise sagt die Fehlereldung tatsächlich etwas aus, was direkt mit der Ursache zu tun hat.

Lösung

Je nach Situation kann es verschiedene Lösungsansätze geben, warum der Server die URL nicht erreichen kann. In unserem Fallbeispiel lag es an einer Firewall-Einstellung.

CMS-Inhalte mit jQuery in eine statische Seite einfügen, CMS-Seite ohne html und body ausgeben

Bei einer stark besuchten Seite sollte eine Cache-Lösung wie Varnish eingesetzt werden. Der Nachteil einer gecachten Seite ist, dass ein Update eines Inhaltsblocks (z.B. einer Ankündigung oder einer Werbaktion), der auf allen Unterseiten angezeigt werden soll, durch den Cache nicht sofort möglich wäre.

Ohne der Erneuerung der gecachten Seiten kann der Inhalt des Blocks über jQuery geladen und in die gecachte Seite eingefügt werden. So entfällt das Cache-Warming, und der neue Inhalt ist sofort auf allen Seiten zu sehen.

Auch der dynamische Inhalt kann über eine CMS-Seite in Magento verwaltet werden. Um ihn über jQuery in die vorhandene Seitenstruktur sauber und effizient einfügen zu können, sollten die Inhalte der CMS-Seite in Roh-Form ausgegeben werden.

Roh-Ausgabe von CMS-Inhalten

Mit dem Layout „Empty“ und durch Entfernen des head-Blocks mit dem folgenden XML-Layout-Update können CMS-Inhalte unformatiert ausgegeben werden:

<remove name="head" />

Wenn wir die Quellecode-Ausgabe dieser CMS-Seite im Frontend ansehen, stellen wir fest, dass der Inhalt in ein nicht ganz leeres Layout eingebettet wird:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    </head>
<body class="page-empty cms-page-view cms-hello">
    <div>
        <div class="std"><p>Hallo Welt</p></div></div>
</body>
</html>

Dies hängt damit zusammen, dass für diese Ausgabe die Vorlage unter app/design/frontend/base/default/template/page/empty.phtml verwendet wird, die in der Tat nicht leer ist:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>">
<head>
    <?php echo $this->getChildHtml('head') ?>
</head>
<body>
    <div>
        <?php echo $this->getChildHtml('after_body_start') ?>
        <?php echo $this->getChildHtml('global_messages') ?>
        <?php echo $this->getChildHtml('content') ?>
        <?php echo $this->getChildHtml('before_body_end') ?>
        <?php echo $this->getAbsoluteFooter() ?>
    </div>
</body>
</html>

Für die dynamische Einbindung der CMS-Inhalte per jQuery in eine gecachte Seite, sollten die CMS-Inhalte in ihrem Ursprungszustand ausgegeben werden, d. h. ohne html- und body-Tag. Dies ist mit dem eigenem wirklich leeren Template möglich:

In unserem Template-Verzeichnis (z. B. app/design/frontend/default/default/template/page) legen wir die Datei really-empty.phtml mit folgendem Inhalt an:

<?php echo $this->getChildHtml('content') ?>

In das Feld XML für Layoutänderungen tragen wir folgenden XML-Code ein:

<remove name="head" />

<reference name="root">
    <action method="setTemplate">
       <template>page/really-empty.phtml</template>
    </action>
</reference>

CMS-Inhalte mit jQuery einfügen

Nun bekommen wir tatsächlich nur <p>Hallo Welt</p> ausgegeben und könnten diese Inhalte dynamisch in eine statische Seite einfügen:

<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$.noConflict();
jQuery(document).ready(function() {
    jQuery('#dynamic-content').load('/magento1702sd/hello');
});
</script>
</head>
<body>

<div id="dynamic-content"></div>

</body>
</html>

Weiterlesen:

Automatische Warnung vor Verwendung geschützter Wortmarken

Hin und wieder bekommen unsere Kunden und auch wir bei unseren eigenen Projekten Abmahnungen wegen Verwendung geschützter Wortmarken. Manchmal sind geschützte Worte so alltäglich, dass der Shopbetreiber nicht mal vermuten würde, dass das Wort geschützt ist.

Wussten Sie z. B. dass der Satz „Berlin gefällt mir“ (s. DPMA-Eintrag) eine geschützte Wortmarke ist? Wenn Sie Ihre Liebe zur Hauptstadt so beispielsweise auf einem T-Shirt zum Ausdruck bringen und es zum Verkauf anbieten, werden Sie ziemlich schnell Post von den Anwälten des Markeninhabers bekommen und es wird richtig teuer. Es gibt überraschend viele Beispiele für Wortmarken dieser Art!

Eine Dienstleistung, die sämtliche Texte des Online-Shops (Produkt-Attribute, CMS-Seiten und statische Blöcke) auf gesicherte Wortmarken prüft und den Betreiber vorwarnt, haben wir nicht gefunden! Entsprechende Filter für Nizza-Klassen, die zum Thema des Shops passen, sollen die Wortmarken thematisch eingrenzen und so die Zahl irrelevanter Warnungen reduzieren.

Welche Marken bereits existieren, können Sie manuell über die DPMA-Einsteigerrecherche herausfinden:

Internationale Recherche ist u. a. mit WIPO möglich.

XAMPP/Apache startet nicht, Skype blockiert Port 80

Fehlerbeschreibung

XAMPP/Apache unter Windows startet nicht. Es wird die folgende Fehlermeldung angezeigt:

Error: Apache shutdown unexpectedly.
This may be due to a blocked port, missing dependencies,
improper privileges, a crash, or a shutdown by another method.
Check the "/xampp/apache/logs/error.log" file
and the Windows Event Viewer for more clues

Mögliche Ursache

Apache wurde bereits gestartet. In diesem Fall ist der Prodzess httpd.exe im Taskmanager auffindbar und kann darüber beendet werden.

Wenn Apache nicht läuft, wird Port 80 möglicherweise von Skype blockiert.

Mögliche Lösung

Im Skype-Menü unter Aktionen > Optionen > Erweitert > Verbindung deaktivieren Sie die Option Ports 80 und 443 für zusätzliche eingehende Verbindungen verwenden und starten Sie Skype neu.

Um Skype neu zu starten, muss Skype beendet werden (es reicht nicht Skype zu schließen). Klicken dazu mit der rechten Maustaste auf Skype in der Taskleiste und dann auf Beenden. Unter Windows 7 sieht das Kontextmenü wie folgt aus:

Weiterlesen:

Magento über das Meta-Modul Mage_All_Latest updaten

Wann haben Sie Magento das letzte Mal über den Download-Manager geupdatet?

Eine tägliche Beschäftigung ist das nicht und so wird gerne vergessen, dass beim Update des gesamten Systems über den Download-Manager nicht jedes einzelne Modul in seiner neuesten Version manuell ausgewählt werden muss, sondern nur das Meta-Paket Mage_All_Latest:

Nach dem Update von Mage_All_Latest werden alle Magento-Core-Module auf die neueste Version geupdatet.

 

Magento-SOAP-Anfragen loggen und debuggen

Bei der Arbeit mit der Magento-API bietet Magento von sich aus keine Debugging-Möglichkeiten. Wer herausfinden will, wie die XML-Anfrage ausschaut, die an Magento gesendet wurde, dem hilft die Erweiterung Inchoo_SoapLogger (GitHub) weiter!

Nach dem Kopieren der Erweiterung in das Magento-Verzeichnis und nach dem Aktualisieren des Magento-Konfigurations-Caches, wird der nächste API-Call in der Datei var/v1_soap.log bzw. var/v2_soap.log (im var-Verzeichnis von Magento) gespeichert. Die Erweiterung verwendet zum Schreiben der Dateien Mage::log, und erfordert, dass der Magento-Log in System > Konfiguration > Erweitert: Entwickleroptionen > Log Einstellungen aktiviert ist:

Wie die Erweiterung im Detail funktioniert, wird vom Entwickler hier dokumentiert.

Erweiterung von GitHub herunterladen.

Fehler bei API-Verbindung mit Magento: Uncaught SoapFault exception: [HTTP] Unable to parse URL in …

Fehlerbeschreibung

Ein PHP-Skript versucht eine API-Verbindung mit einem Magento-Shop herzustellen:

$client = new SoapClient('http://beispiel.de/index.php/api/v2_soap/?wsdl');

Der Versuch endet mit der folgenden Fehlermeldung:

Fatal error: Uncaught SoapFault exception: [HTTP] Unable to parse URL in ...script.php

Es kommt zu dem Fehler, obwohl die URL http://beispiel.de/index.php/api/v2_soap/?wsdl erreichbar ist und einen gültigen XML-Code zurückgibt.

Ursache

Suchen Sie in der XML-Ausgabe unter http://beispiel.de/index.php/api/v2_soap/?wsdl nach soap:address:

<service name="MagentoService">
    <port name="Mage_Api_Model_Server_V2_HandlerPort" binding="typens:Mage_Api_Model_Server_V2_HandlerBinding">
        <soap:address location="https://beispiel.de/index.php/api/v2_soap/index/"/>
    </port>
</service>

Der Wert des Attributs location von soap:address muss eine gültige URL enthalten:

  • Gültige URL:
    https://beispiel.de/index.php/api/v2_soap/index/
  • Ungültige URL, Fall 1: Abweichende Basis-URL:
    https://andere-domain.de/index.php/api/v2_soap/index/
  • Ungültige URL, Fall 2: Basis-URL fehlt:
    /api/v2_soap/index/

Lösung

Der Wert des Attributs location von soap:address muss eine absolute und gültige URL enthalten!

  • Der 1. Fall ist der häufigste, bei dem die URL unter der die API aufgerufen wurde, nicht mit der in der Systemkonfiguration eingestellten URL übereinstimmt (dies kommt typischerweise nach einem Domain-Umzug oder in einer Testumgebung vor, die unter einer anderen Domain erreichbar ist).

Überprüfen und ändern Sie ggf. die Einstellung unter System > Konfiguration > Allgemein: Web > Ungesichert/Gesichert > Basis-URL.

  • Der 2. Fall ist sehr speziell, da bei diesem Projekt die Basis-URL bei der Ausgabe im gesamten Shop-Frontend (aufgrund bestimmter Anforderungen) entfernt wird.

Durch Anpassung des Ausgabefilters, sodass die Basis-URL bei API-Ausgaben nicht entfernt wird, konnte der Fehler in diesem Fall behoben werden.

Weiterführende Links:

Magento-API: Bestellte Produkte mit parent_item_id abfragen

Bestellungen können in Magento über die API ausgelesen und von einer externen Software (z. B. Warenwirtschaft, Fakturierung) importiert werden.

Wenn in einer Bestellung jedoch zusammengesetzte Produkte wie Bündelartikel (bundle product) enthalten sind, wird die Spalte parent_item_id bei der API-Abfrage der Bestellposten standardmäßig nicht übermittelt. Diese Spalte ist aber entscheidend, um einzelne Teilprodukte dem Hauptprodukt zuordnen zu können!

Dies hängt damit zusammen, dass jedes Attribut für die Ausgabe explizit in der WSDL-Definitionsdatei angegeben werden muss. In der mitgelieferten WSDL-Definition für Bestellposten wurde die Spalte parent_item_id offenbar vergessen. In der aktuellen Magento-Version 1.9.0.1 (vom 16.05.2014) ist das immer noch der Fall.

Durch eine Anpassung der WSDL-Definition, kann die Spalte parent_item_id (und weitere Spalten, die in der SQL-Tabelle sales_flat_order_item zu finden sind) nachträglich eingefügt und über die API ausgegeben werden.

Dazu wird die Datei app/code/core/Mage/Sales/etc/wsdl.xml direkt im core-Verzeichnis angepasst! Die angepasste Datei app/code/local/Mage/Sales/etc/wsdl.xml im local-Code-Pool wird von Magento (nach Löschung des gesamten Magento-Caches) leider ignoriert. Wenn es eine elegantere Methode für diese Anpassung gibt, freuen wir uns über Ihre Kommentare.

Die fehlende Spalte parent_item_id wird ähnlich wie andere Spalten (z. B. item_id) hinzugefügt.

<complexType name="salesOrderItemEntity">
    <all>
        ...
        <element name="parent_item_id" type="xsd:string" minOccurs="0" />
        ...

Der guten Ordnung halber haben wir die Spalte parent_item_id genau dort eingefügt, wo sie in der ursprünglichen Tabelle sales_flat_order_item zu finden ist, zwischen order_id und quote_item_id:

<complexType name="salesOrderItemEntity">
    <all>
        <element name="item_id" type="xsd:string" minOccurs="0" />
        <element name="order_id" type="xsd:string" minOccurs="0" />
        <element name="parent_item_id" type="xsd:string" minOccurs="0" />
        <element name="quote_item_id" type="xsd:string" minOccurs="0" />
        <element name="created_at" type="xsd:string" minOccurs="0" />
        <element name="updated_at" type="xsd:string" minOccurs="0" />
        <element name="product_id" type="xsd:string" minOccurs="0" />
        <element name="product_type" type="xsd:string" minOccurs="0" />
        <element name="product_options" type="xsd:string" minOccurs="0" />
        <element name="weight" type="xsd:string" minOccurs="0" />
        <element name="is_virtual" type="xsd:string" minOccurs="0" />
        <element name="sku" type="xsd:string" minOccurs="0" />
        <element name="name" type="xsd:string" minOccurs="0" />
        <element name="applied_rule_ids" type="xsd:string" minOccurs="0" />
        <element name="free_shipping" type="xsd:string" minOccurs="0" />
        <element name="is_qty_decimal" type="xsd:string" minOccurs="0" />
        <element name="no_discount" type="xsd:string" minOccurs="0" />
        <element name="qty_canceled" type="xsd:string" minOccurs="0" />
        <element name="qty_invoiced" type="xsd:string" minOccurs="0" />
        <element name="qty_ordered" type="xsd:string" minOccurs="0" />
        <element name="qty_refunded" type="xsd:string" minOccurs="0" />
        <element name="qty_shipped" type="xsd:string" minOccurs="0" />
        <element name="cost" type="xsd:string" minOccurs="0" />
        <element name="price" type="xsd:string" minOccurs="0" />
        <element name="base_price" type="xsd:string" minOccurs="0" />
        <element name="original_price" type="xsd:string" minOccurs="0" />
        <element name="base_original_price" type="xsd:string" minOccurs="0" />
        <element name="tax_percent" type="xsd:string" minOccurs="0" />
        <element name="tax_amount" type="xsd:string" minOccurs="0" />
        <element name="base_tax_amount" type="xsd:string" minOccurs="0" />
        <element name="tax_invoiced" type="xsd:string" minOccurs="0" />
        <element name="base_tax_invoiced" type="xsd:string" minOccurs="0" />
        <element name="discount_percent" type="xsd:string" minOccurs="0" />
        <element name="discount_amount" type="xsd:string" minOccurs="0" />
        <element name="base_discount_amount" type="xsd:string" minOccurs="0" />
        <element name="discount_invoiced" type="xsd:string" minOccurs="0" />
        <element name="base_discount_invoiced" type="xsd:string" minOccurs="0" />
        <element name="amount_refunded" type="xsd:string" minOccurs="0" />
        <element name="base_amount_refunded" type="xsd:string" minOccurs="0" />
        <element name="row_total" type="xsd:string" minOccurs="0" />
        <element name="base_row_total" type="xsd:string" minOccurs="0" />
        <element name="row_invoiced" type="xsd:string" minOccurs="0" />
        <element name="base_row_invoiced" type="xsd:string" minOccurs="0" />
        <element name="row_weight" type="xsd:string" minOccurs="0" />
        <element name="gift_message_id" type="xsd:string" minOccurs="0" />
        <element name="gift_message" type="xsd:string" minOccurs="0" />
        <element name="gift_message_available" type="xsd:string" minOccurs="0" />
        <element name="base_tax_before_discount" type="xsd:string" minOccurs="0" />
        <element name="tax_before_discount" type="xsd:string" minOccurs="0" />
        <element name="weee_tax_applied" type="xsd:string" minOccurs="0" />
        <element name="weee_tax_applied_amount" type="xsd:string" minOccurs="0" />
        <element name="weee_tax_applied_row_amount" type="xsd:string" minOccurs="0" />
        <element name="base_weee_tax_applied_amount" type="xsd:string" minOccurs="0" />
        <element name="base_weee_tax_applied_row_amount" type="xsd:string" minOccurs="0" />
        <element name="weee_tax_disposition" type="xsd:string" minOccurs="0" />
        <element name="weee_tax_row_disposition" type="xsd:string" minOccurs="0" />
        <element name="base_weee_tax_disposition" type="xsd:string" minOccurs="0" />
        <element name="base_weee_tax_row_disposition" type="xsd:string" minOccurs="0" />
    </all>
</complexType>

Nach dieser Anpassung muss neben dem Magento-Cache „Konfiguration“ in System > Cache-Verwaltung, noch der WSDL-Cache geleert werden, sofern dieser aktiviert war. Die Einstellung dazu finden Sie in System > Konfiguration > Services: Magento Core API:

Für bessere Performance der API-Abfragen empfehlen wir den WSDL-Cache zu aktivieren bzw. aktiviert zu lassen. Der WSDL-Cache wird durch Deaktivierung im Magento-Backend nicht gelöscht! Dazu müssen die Dateien im Systemverzeichnis des WSDL-Cache über die Kommandozeile entfernt werden. Je nach Serverkonfiguration, kann der WSDL-Cache sich in einem anderen Verzeichnis befinden, das über phpinfo() herausgefunden werden kann:

In unserer Serverumgebung befindet sich der WSDL-Cache in /tmp (STRG+F nach soap.wsdl_cache_dir) und kann wie folgt geleert werden:

cd /tmp
rm -rf *

Mit dem folgenden Beispielcode kann ein PHP-Client einzelne Produkte einer Bestellung ausgeben:

<pre><?php
$client = new SoapClient('http://beispiel.de/index.php/api/v2_soap/?wsdl');
$session = $client->login('api-user', 'geheim123');

$result = $client->salesOrderInfo($session, '1000000001');
$items = $result->items;
foreach($items as $item) {
    print 'item_id:'.$item->item_id."\n";
    print 'product_type:'.$item->product_type."\n";
    if(isset($item->parent_item_id)) print 'parent_item_id:'.$item->parent_item_id."\n";
    print 'name:'.$item->name."\n";
    print "--------------------------\n";
}

In der Beispielausgabe ist das erste Produkte mit der item_id=10 ein Bündelprodukt, welches such aus den unterstehenden einfachen Produkten zusammensetzt, was über die parent_item_id=10 nachvollziehbar ist:

item_id:10
product_type:bundle
name:Beispiel-Bündelprodukt
--------------------------
item_id:5
product_type:simple
parent_item_id:10
name:Einfaches-Produkt-1
--------------------------
item_id:12
product_type:simple
parent_item_id:10
name:Einfaches-Produkt-2
--------------------------
item_id:7
product_type:simple
parent_item_id:10
name:Einfaches-Produkt-3

Varnish-Neustart: Running VCC-compiler failed, exit 1 – VCL compilation failed

Fehlerbeschreibung

Scheinbar nach einer Anpassung der VCL-Datei startet Varnish nicht und meldet einen Fehler ohne genauer Beschreibung oder Zeilenreferenz:

root@test-01:~# service varnish restart
 * Stopping HTTP accelerator varnishd              [fail]
 * Starting HTTP accelerator varnishd              [fail]
Running VCC-compiler failed, exit 1

VCL compilation failed

Der Wechsel der VCL-Datei auf eine frühere Version mit der Varnish auf der gleichen Maschine bereits funktioniert hatte, behebt das Problem nicht.

Mögliche Ursache

Auf der genutzten Partition steht nicht genug Speicherplatz zur Verfügung.

Lösung

apt-get remove varnish brachte in diesem Fall den entscheidenden Hinweis:

Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be REMOVED:
  varnish
0 upgraded, 0 newly installed, 1 to remove and 0 not upgraded.
After this operation, 1,288 kB disk space will be freed.
Do you want to continue [Y/n]? Y
cut: write error: No space left on device
E: Problem executing scripts DPkg::Pre-Invoke 'if [ -x /usr/bin/etckeeper ]; then etckeeper pre-install; fi'
E: Sub-process returned an error code

cut: write error: No space left on device, d. h. der Speicher ist voll. Der folgende Befehl liefert eine Übersicht aller Partitionen und Speicherverbrauch:

df -h

Nach Freigabe des Speichers auf der Partition, die in das Verzeichnis var gemountet war, konnte Varnish fehlerfrei neugestartet werden.

Call to a member function getMetaTitle() on a non-object

Fehlermeldung

Fatal error: Call to a member function getMetaTitle() on a non-object
in app/code/core/Mage/Catalog/Block/Category/View.php on line 44

Ursache

Der Fehler tritt beim Zugriff auf den Metatitel der aktuellen Kategorie in Mage_Catalog_Block_Category_View:

$category = $this->getCurrentCategory();
if ($title = $category->getMetaTitle()) {
   $headBlock->setTitle($title);
}

Ist das Produkt der Website zugeordnet aber keiner Kategorie darin, kommt es zu diesem Fehler, sofern im Template auf die eigenschaften der aktuellen Kategorie zugegriffen wird.

Lösung

Wenn der Artikel absichtlich in keiner Kategorie gelistet, aber direkt aufgerufen können werden soll, kann der Artikel der Wurzelkategorie der zugehörigen Website zugeordnet werden.

Memcached und Magento: Can’t get filling percentage

Beispielfehlermeldung

Can't get filling percentage

Ursache

Memcached läuft nicht oder wurde neugestartet.

Fehlerbehebung

Memecached und danach Apache neu starten:

sudo service memcached restart
sudo service apache2 restart

Konfiguration und Ressourcen überprüfen.

Marktanteil von Magento, Update 2014

Im Juni 2012 haben wir das erste Mal den Marktanteil von Magento dokumentiert.

Marktanteile nach Wappalyzer

Nach Wappalyzer ist der prozentuelle Anteil der Magento-Shops auf 22% gesunken, bei absoluten Werten bleibt Magento aber weiterhin mit Abstand die ungeschlagene Nummer eins. Top 10 E-Commerce-Anwendungen nach Wappalyzer (Momentaufnahme vom 30.05.2014):

Platz Anwendung Websites Besuche
1 Magento 113.384 14.422.458
2 OpenCart 7.488 5.140.300
3 Prestashop 74.256 4.541.016
4 WooCommerce 6.318 5.886.903
5 osCommerce 39.522 1.562.696
6 Shopify 25.253 1.240.091
7 VirtueMart 20.467 570.695
8 Bigcommerce 19.839 939.041
9 Zen Cart 11.283 39.931
10 eZ Publish 8.615 970.309

Auch interessant ist die Verteilung der Sprachen, in den die Shop-Systeme betrieben werden. Die Spracherkennung von Wappalyzer scheint aber nicht immer zuverlässig zu sein. Für WooCommerce fehlt das Diagramm völlig, für OpenCart erscheint die Statistik nicht plausibel:

Google Trends in Deutschland und weltweit

Diese Popularität von Magento wird von Google Trends bestätigt:

Seit einiger Zeit feiert sich Magento Connect als „… the largest eCommerce application marketplace in the world“.

SISTRIX-Sichtbarkeit in Google.de und Google.com

Die nachfolgenden Charts zeigen den zeitlichen Verlauf der SISTRIX-Sichtbarkeit in Google.de und Google.com für die Hauptseiten der Top 3 E-Commerce-Anwendungen Magento, OpenCart und Prestashop.

Vereinfacht beschrieben, errechnet sich die SISTRIX-Sichtbarkeit aus der Anzahl der Platzierungen in den Top 100 für ein Katalog von 250.000+ Suchbegriffen gewichtet mit der Popularität des jeweiligen Suchworts. In die Berechnunng fließen noch weitere Faktoren ein wie die Platzierung an sich. Aus der Sichtbarkeit kann u. a. die relative Anzahl der Besucher geschätzt werden.

Durch Umstrukturierung der Webpräsenz und teilweisen Umzug der Inhalte von magentocommerce.com auf magento.com hat die Magento-Hauptseite Platzierungen und damit Sichtbarkeit bei Google.de verloren:

Bei Google.com bleibt Magento hinsichtlich der Sichtbarkeit auch heute noch klarer Sieger (die Sichtbarkeit der Seiten in Google.com wird erst seit 2011 aufgezeichnet):

Weiterlesen: Magento-Marktanteile in 2012 und 2013.

Schnelle Suche in System-Einstellungen: Admin Config Quick Search

Admin Config Quick Search auf Magento Connect
Mit der Erweiterung Admin Config Quick Search (GitHub: Magento Quick Config Module) können die Systemeinstellungen von Magento schnell durchsucht werden.

Dies kann einem Magento-Einsteiger, der noch nicht alle Systemeinstellungen kennt, sehr viel Zeit sparen. Aber auch einem Dienstleister, der mehrere Magento-Shops verwaltet, die jeweils eine individuelle Modifikation und Zusammenstellung von Magento-Erweiterungen mit speziellen Systemeinstellungen aufweisen, stellt diese Suchfunktion einen Mehrwert dar.

Nach der Einrichtung der Erweiterung taucht in den Systemeinstellungen oben rechts ein Suchfeld auf. Die Suche wird während der Eingabe ausgeführt:

Download der Erweiterung:

Weiterlesen: Erweiterungen von Magento Connect herunterladen.

Magento-Erweiterungen herunterladen und analysieren

Magento Connect bietet leider keine Möglichkeit eine gepackte Erweiterung direkt herunterzuladen, um sie vor der Installation in den Shop analysieren zu können.

Eine Möglichkeit wäre die Erweiterung über freegento.com herunterzuladen. Die andere Möglichkeit bietet Magento selbst: Die Erweiterung kann über den Download-Manager in Ihrer Magento-Testumgebung heruntergeladen und installiert werden. Die gepackte tgz-Datei ist danach im Magento-Verzeichnis unter downloader/.cache/community zu finden!

Unsere Empfehlung für eine sichere Installation von Magento-Erweiterungen:

  1. Reviews und Kommentare auf Magento Connect lesen
  2. Veröffentlichungsdatum der letzten Version der Erweiterung nachschlagen
  3. Automatisch ermittelte Qualität der Erweiterung bei Judge nachschlagen
  4. Die Erweiterung herunterladen (s. o.) und den Code von einem Magento-Entwickler überfliegen lassen, insbesondere nach curl-Aufrufen und externen Ressourcen (darunter externe Logo-Grafiken) Ausschau halten!
  5. In einer Magento-Entwicklungsumgebung installieren und vollständig testen
  6. Produktivumgebung sichern
  7. In die Produktivumgebung übertragen
  8. Cache-Aktualisierung
  9. Vollständiger Test der Produktivumgebung

Shop während der Entwicklung vor Google-Indizierung und unerwünscheten Besuchern schützen

Mit dem HTTP-Passwort lässt sich der Shop in der Aufbauphase gut vor unerwünschten Besuchern und Indizierung durch Suchmaschinen schützen.

Der etwas umständlichere Weg ist die Einrichtung des HTTP-Passwors über die Server-Konfigurationsdateien .htaccess und .htusers (mehr Details dazu bei SELFHTML).

Da in Magento alle relevanten Anfragen über die index.php abgearbeitet werden (Single Point of Entry), kann der Passwort-Schutz direkt dort umgesetzt werden. Dazu kann am Anfang der index.php nach dem <?php der folgende Code eingebaut werden:

<?php

define('ADMIN_USERNAME','user');
define('ADMIN_PASSWORD','geheim123');

if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) ||
        $_SERVER['PHP_AUTH_USER'] != ADMIN_USERNAME ||$_SERVER['PHP_AUTH_PW'] != ADMIN_PASSWORD) {

    Header("WWW-Authenticate: Basic realm=\"Magento Development Environment\"");
    Header("HTTP/1.0 401 Unauthorized");
    echo 'Access Denied. ' . time();
    exit;
}

Damit werden Frontend und Backend geschützt. Doch im Backend kann der Flash-Uploader für Produktbilder mit dem Passwortschutz nicht umgehen und meldet den Fehler „Upload HTTP Error“:

Da der Zugang zum Backend bereits geschützt ist, kann er durch eine entsprechende Erweiterung des Codes vom HTTP-Passwortschutz ausgeschlossen werden:

define('ADMIN_URL','admin');
define('ADMIN_USERNAME','user');
define('ADMIN_PASSWORD','geheim123');

if(strpos($_SERVER['REQUEST_URI'], ADMIN_URL) === false) {
    if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) ||
               $_SERVER['PHP_AUTH_USER'] != ADMIN_USERNAME ||$_SERVER['PHP_AUTH_PW'] != ADMIN_PASSWORD) {

        Header("WWW-Authenticate: Basic realm=\"Magento Development Environment\"");
        Header("HTTP/1.0 401 Unauthorized");
        echo 'Access Denied. ' . time();
        exit;
    }
}

SEPA-Lastschrift mit DebitPayment

Mit der kostenfreien Erweiterung DebitPayment ist die grundlegende Abwicklung der Zahlung per Lastschrift möglich.

In System > Konfiguration > Verkäufe: Zahlarten > Bankeinzug bietet die Erweiterungen eine reihe praktischer Einstellmöglichkeiten, darunter Anzeige abhängig von der Kundengruppe und Mindest- und Höchstbestellwerte:

Nach der Aktivierung und Umstellung der Art auf SEPA, wird im Checkout die Eingabe der IBAN und BIC möglich:

Die Prüfung der Eingabe findet leider nicht statt. Die Eingabefelder sind zwar als Pflichtfelder eingestellt, doch bereits Eingabe von einem Zeichen in jedes Feld wird akzeptiert:

Die Bankverbindung wird in den Bestelldetails gespeichert. In diesem Fall wurde in jedes Feld ein „.“ eingegeben:

Um die Export-Funktionalität zu nutzen, muss unter System > Konfiguration > Verkäufe: Zahlarten > Bankeinzug / Lastschrift die Bankverbindung des Shop-Betreibers eingetragen zu werden:

Danach können SEPA-Lastschrift-Daten in Verkäufe > Lastschrift-Bestellungen im CSV-Format wie folgt exportiert werden:

  1. Bestellungen abgleichen
  2. Nicht exportierte Bestellungen (Status-Spalte) filtern und wählen
  3. CSV-Export

Die Erteilung des SEPA-Mandats müsste der Shop-Betreiber manuell durchführen.

Weiterlesen:

Verteilung der E-Mail-Anbieter unter Newsletter-Abonnenten und Kunden

Beim Versand von Newslettern ist es praktisch zu wissen, welche Email-Anbieter die Kunden verwenden. Die folgende Abfrage extrahiert die Domain des Anbieters und zählt die Email-Adressen der Newsletter-Abonnenten:

SELECT provider_domain AS provider, count(subscriber_email) AS number FROM (
    SELECT
    subscriber_email,
    SUBSTRING_INDEX(subscriber_email, '@', -1) provider_full,
    SUBSTRING_INDEX(SUBSTRING_INDEX(subscriber_email, '@', -1), '.', -2) provider_domain
    FROM newsletter_subscriber
) AS t
GROUP BY provider
ORDER BY number DESC;

Leicht angepasst, kann diese Abfrage auf alle Kunden angewendet werden:

SELECT provider_domain AS provider, count(email) AS number FROM (
    SELECT
    email,
    SUBSTRING_INDEX(email, '@', -1) provider_full,
    SUBSTRING_INDEX(SUBSTRING_INDEX(email, '@', -1), '.', -2) provider_domain
    FROM customer_entity
) AS t
GROUP BY provider
ORDER BY number DESC;

Weiter optimiert, fasst die Anfrage Anbieter mit weniger als 50 Email-Adressen in eine Zeile mit dem Eintrag „OTHER“ zusammen:

SELECT provider, SUM(number) number FROM (
SELECT IF(number < 50, '* OTHER', provider) provider, number FROM (
  SELECT provider_domain provider, GROUP_CONCAT(DISTINCT provider_full) domains, COUNT(email) AS number FROM (
    SELECT
    email,
    SUBSTRING_INDEX(email, '@', -1) provider_full,
    SUBSTRING_INDEX(SUBSTRING_INDEX(email, '@', -1), '.', -2) provider_domain
    FROM customer_entity
  ) AS t
  GROUP BY provider_domain
) AS v
) AS w
GROUP BY provider
ORDER BY number DESC;

Einige Anbieter-Domains (z.B. gmail und googlemail) sollten noch manuell zusammengefasst werden.

Um auch noch die Summe anzeigen zu lassen, kann WITH ROLLUP verwendet werden. Um die Liste absteigend sortiert zu lassen, ist allerdings eine weitere Schachtelung nötig:

SELECT IF(provider IS NULL, '* TOTAL', provider) provider, number FROM (
SELECT provider, SUM(number) number FROM (
SELECT IF(number < 50, '* OTHER', provider) provider, number FROM (
  SELECT provider_domain provider, GROUP_CONCAT(DISTINCT provider_full) domains, COUNT(email) AS number FROM (
    SELECT
    email,
    SUBSTRING_INDEX(email, '@', -1) provider_full,
    SUBSTRING_INDEX(SUBSTRING_INDEX(email, '@', -1), '.', -2) provider_domain
    FROM customer_entity
  ) AS t
  GROUP BY provider_domain
) AS v
) AS w
GROUP BY provider
WITH ROLLUP
) AS x
ORDER BY number DESC

Varnish und Google Analytics

Links mit Analytics-Kamagnen-Parametern werden in der Standard-Konfiguration von Varnish nicht als Seiten mit identischem Inhalt erkannt. Die beiden nachfolgenden URL werden von Varnish als verschiedene Seiten erkannt und deshalb unabhängig voneinander gecacht:

  1. URL: http://xonu.de
  2. URL: http://xonu.de?utm_campaign=ny2014&utm_medium=banner&utm_source=extern

Während die erste URL von einem Cache-Warmer vorbereitet wird, ist die zweite URL dem Cache-Warmer typischerweise nicht bekannt, sodass der erste Besucher etwas länger auf die Inhalte warten muss, obwohl sie aus dem Cache hätten geliefert werden können.

Damit dies geschieht, muss Varnish mitgeteilt werden, dass Analytics-Parameter den Inhalt nicht verändern. Varnish berechnet einen Hash basierend auf der angefragten URL. Diese URL kann in vcl_recv vor der Berechnung des Hashs manipuliert werden.

Durch Entfernen aller Analytics-Parameter aus der URL wird dem Varnish mitgeteilt, dass sie die Inhalte nicht verändert, sodass für die erste und zweite URL gleiche Inhalte ausgeliefert werden können.

In vcl_recv wird der folgende Zusatzblock hinzugefügt:

sub vcl_recv {

  if (req.url ~ "(\?|&)(utm_[^=]+)=") {
    set req.url = regsub(req.url, "\\\?", "?");
    set req.url = regsuball(req.url, "&(utm_[^=]+)=([^&]+)", "");
    set req.url = regsuball(req.url, "\?(utm_[^=]+)=([^&]+)", "?");
    set req.url = regsub(req.url, "\?&", "?");
    set req.url = regsub(req.url, "\?$", "");
  }

  ....

}

Alle Analytics-Parameter (s. Analytics URL Builder) starten mit „utm_“:

  • utm_source: Kampagnenquelle
  • utm_medium: Kampagnenmedium
  • utm_term: Kampagnenbegriff
  • utm_content: Kampagnen-Content
  • utm_campaign: Kampagnenname

Wird also ein solcher Parameter erkannt, wird die URL davon bereinigt:

  • Zeile 4 ersetzt /? durch ? (ohne Slash).
  • Zeile 5 entfernt Analytics-Parameter, die nicht an erster Stelle stehen. In URL 2 werden hierdurch &utm_medium=banner und &utm_source=extern entfernt.
  • Zeile 6 entfernt Analytics-Parameter, die an erster Stelle stehen. In URL 2 wird dadurch ?utm_campaign=ny2014 entfernt.
  • Zeile 7 entfernt das überflüssige &-Zeichen, wenn die URL neben Analytics nicht weitere GET-Parameter an nicht-erster Stelle enthalten hatte (aus ?utm_campaign=ny2014&p=2 wäre ohne dieser Zeile ?&p=2 geworden).
  • In dem Fall, dass alle GET-Parameter in der URL Analytics-Parameter waren, entfernt die Zeile 8 das übrig gebliebene ?-Zeichen.

Da die Auswertung der Kampagnen-Parameter durch den Analytics-Tracker Client-seitig durch JavaScripte geschieht, bleibt das Tracking davon unbetroffen.

Update auf 1.8.1 und Kunden können sich nicht mehr einloggen, Magento-Frontend-Login funktioniert nicht mehr

Berichte häufen sich, dass das Frontend-Login nach Update auf Magento 1.8.1 nicht mehr funktioniert, d. h. nach Eingabe der richtigen Login-Daten wird ohne Fehlermeldungen erneut das Login-Formular angezeigt, während bei Eingabe falscher Benutzerdaten eine entsprechende Fehlermeldung angezeigt wird.

In einem Shop konnten wir den Fehler durch Hinzufügen des folgenden hidden-Felds in das Login-Formular beheben:

<input type="hidden" name="form_key" value="<?php echo Mage::getSingleton('core/session')->getFormKey(); ?>" />

Pfad der zu bearbeitenden Template-Datei:

app/design/frontend/base/default/template/persistent/customer/form/login.phtml

Der Code kann direkt in der nächsten Zeile nach nach <form …> eingefügt werden. Vor dem Neuladen des Formulars im eigenen Browser kann das Löschen von Cookies hilfreich sein.

Über FireBug können dabei gezielt die Cookies der Shop-Domain gelöscht werden:

Texthinweise im Magento-Checkout: Statische Blöcke oberhalb und unterhalb der Zusammenfassung einbauen

Diese Anleitung zeigt, wie man eigene Texthinweise im letzten Checkout-Schritt anzeigen lassen kann. Die Inhalte werden über statische Blöcke verwaltet, die dann über ein Layout-Update eingebunden werden.

Wir gehen davon aus, dass im Shop German Setup verwendet wird, es ist aber keine Voraussetzung. Wir wollen oberhalb der Zusammenfassung „Bei Zahlungseingang vor 15:00 Uhr erfolgt die Lieferung noch am gleichen Tag.“ und unterhalb „Bei Lieferungen in das Nicht-EU-Ausland fallen zusätzliche Zölle, Steuern und Gebühren an.“ anzeigen. Der zweite Hinweis ist für das Bestehen der TrustedShops-Zertifizierung erforderlich.

Der nachfolgende Screenshot demonstriert das Ergebnis:

Texte als statische Blöcke

Statische Blöcke lassen sich im Backend bequem verwalten. Deshalb sollen die beiden Hinweise als statische Blöcke angelegt werden. Wir legen zwei statische Blöcke mit den folgenden Bezeichnern an (die Namen wurden frei gewählt):

  • checkout_review_before
  • checkout_review_after

Der Inhalt des Blocks checkout_review_before ist:

<div style="border-color: #D9DDE3; border-style: solid; border-width: 0 1px; padding: 7px;">
Bei Zahlungseingang vor 15:00 Uhr erfolgt die Lieferung noch am gleichen Tag.
</div>

Der Inline-Style wurde aus der CSS-Regel-Definition für .opc .checkout-agreements aus styles.css übernommen, der CSS-Datei des Magento-Base-Themes. Die Gestaltung sorgt für die Fortsetzung des hellgrauen Rahmens und des Abstands des Texts vom Rand.

Der Inhalt des Blocks checkout_review_after ist:

<div style="border-width: 0 2px; border-style: none solid; padding-left: 10px;">
Bei Lieferungen in das Nicht-EU-Ausland fallen zus&auml;tzliche Z&ouml;lle, Steuern und Geb&uuml;hren an.
</div>

Auch hier sorgt der Inline-Style für die Fortsetzung des schwarzen Rahmens, der nach der Button-Lösung vorgeschrieben und für das Bestehen der TrustedShops-Zertifizierung erforderlich ist.

Der schwarze Rahmen wird von German Setup in skin/frontend/base/default/css/germansetup/checkout.css gestaltet. Der Inline-Style wurde von dort übernommen.

Statische Blöcke über Layout-Update im Checkout anzeigen

Über ein Layout-Update, ganz ohne Template-Anpassung, lassen sich statische Blöcke in Checkout-Schritte integrieren. Die vorher angelegte statische Blöcke können mit dem folgenden XML-Layout-Code oberhalb und unterhalb des letzten Checkout-Schritts eingebaut werden:

<?xml version="1.0"?>
<layout version="0.1.0">

    <checkout_onepage_review>
        <reference name="checkout.onepage.review.info.items.before">
            <block type="cms/block" name="checkout_review_before_block" before="-">
                <action method="setBlockId">
                    <block_id>checkout_review_before</block_id>
                </action>
            </block>
        </reference>

        <reference name="checkout.onepage.review.info.items.after">
            <block type="cms/block" name="checkout_review_after_block" after="-">
                <action method="setBlockId">
                    <block_id>checkout_review_after</block_id>
                </action>
            </block>
        </reference>
    </checkout_onepage_review>

</layout>

Der XML-Layout-Code wird in der Datei local.xml in app/design/frontend/default/default/layout/ oder im entsprechenden Theme-Verzeichnis gespeichert. Legen Sie die Datei an, falls sie noch nicht existiert.

Der Layout-Handle checkout_onepage_review und die beiden referenzierten Blöcke checkout.onepage.review.info.items.before und checkout.onepage.review.info.items.after werden in app/design/frontend/base/default/layout/checkout.xml erstmalig definiert und von German Setup in germansetup.xml (gleiches Verzeichnis) noch weiter modifiziert. Diese Anleitung wird also auch ohne installierten German Setup funktionieren.

Die beiden statischen Blöcke werden über XML-Layout-Code definiert. Da die Blöcke checkout.onepage.review.info.items.before und checkout.onepage.review.info.items.after als Strukturblöcke definiert wurden (type=core/text_list), werden die Inhalte aller untergeordneten Blöcke, also auch der statischen Blöcke, ausgegeben.

Nach dem Update des Layout-Cache in System > Cache Verwaltung sind die beiden Blöcke im Checkout sichtbar.

 

Weiterlesen:

Google Sitemaps und internationale Magento-Shops mit mehreren Domains

Google Webmaster Tools helfen die Indizierung eines Shops zu überwachen und zu kontrollieren. Wenn Sie einen internationalen Magento-Shop betreiben/einrichten, der unter verschiedenen Top-Level-Domains erreichbar sein soll, müssen einige Details bei der Verwaltung von Sitemaps beachtet werden.

Die sitemap.xml ist dabei sehr hilfreich und kann von Magento automatisch erstellt werden: Katalog > Google Sitemap.

Auch wenn Magento bei der Sitemap-Erstellung ein Feld für den Dateinamen anbietet, muss die XML-Datei unbedingt sitemap.xml heißen. Bei Verwendung eines anderen Namens, z. B. sitemap_de.xml, bekommt man eine seltsame Fehlermeldung:

Pfad „…“ ist nicht verfügbar und kann nicht verwendet werden.

Englische Version:

Path „…“ is not available and cannot be used.

Die Sitemaps für die verschiedenen Shops der gleichen Magento-Installation können in verschiedenen Unterverzeichnissen des Shop-Verzeichnisses erstellt werden:

  • sitemap/en/sitemap.xml
  • sitemap/de/sitemap.xml

Die Sitemaps sollen in die robots.txt eingetragen werden:

Sitemap: /sitemap/sitemap.xml

Da unter verschiedenen Shop-Domains die gleiche Datei robots.txt vorliegt, können mehrere Sitemaps in der robots.txt eingetragen werden. Laut sitemaps.org: Sitemaps & Cross Submits muss in diesem Fall der vollständige Domainname eingetragen werden:

Sitemap: http://www.mein-shop.de/sitemap/de/sitemap.xml
Sitemap: http://www.mein-shop.de/sitemap/en/sitemap.xml

Dabei ist es unerheblich, welche der Shop-Domains hier verwendet wird; Auch diese Variante wäre zulässig:

Sitemap: http://www.mein-shop.de/sitemap/de/sitemap.xml
Sitemap: http://www.mein-shop.com/sitemap/en/sitemap.xml

Wenn Sie unter der jeweiligen Shop-Domain jeweils die passende Sitemap unter der gleichen URL verwenden wollen, lässt sich dies über eine .htaccess-Weiche realisieren:

sitemap/.htaccess

RewriteEngine on

RewriteCond %{HTTP_HOST} ^(www.)?mein-shop.de$
RewriteRule sitemap.xml de/sitemap.xml [L]

RewriteCond %{HTTP_HOST} ^(www.)?mein-shop.com$
RewriteRule sitemap.xml en/sitemap.xml [L]

RewriteRule .* / [L]

robots.txt

Sitemap: /sitemap/sitemap.xml

Die beiden Sitemaps befinden sich weiterhin in den Unterverzeichnissen en und de:

  • sitemap/en/sitemap.xml
  • sitemap/de/sitemap.xml
  • sitemap/.htaccess

Mit der .htaccess-Weiche können wir die Sitemap unter http://www.mein-shop.de/sitemap/sitemap.xml und unter http://www.mein-shop.com/sitemap/sitemap.xml (ohne Angabe der Unterverzeichnisse en bzw. de) aufrufen und bekommen die sitemap.xml jeweils aus dem passenden Unterverzeichnis ausgegeben.

Damit die Sitemap regelmäßig automatisch erneuert wird, kann dazu ein Magento-interner Cronjob konfiguriert werden:
System > Konfiguration > Katalog: Google Sitemap > Einstellungen für die Erstellung

Die Voraussetzung, dass die Sitemap dann tatsächlich automatisch erstellt wird, ist der regelmäßige Aufruf der cron.php bzw. cron.sh im Hauptverzeichnis von Magento durch Crontab.

Um Ihren Shop vor DDoS-Attaken durch häufigen Aufruf von cron.php zu Schützen, sollten die Zugriffsrechte der cron.php so konfiguriert sein, dass sie nicht im Browser aufgerufen werden kann. Alternativ kann sie z.B. in cron123.php umbenannt werden (der Inhalt von cron.sh muss angepasst werden).

Gerne beraten und unterstützen wir Sie bei der Suchmaschinenoptimierung Ihres Online-Shops.

JavaScript und CSS über das Layout-XML-Code hinzufügen

Layout XML kann im Backend von Magento in Kategorien, Produkten und CMS-Seiten verwendet werden. XML-Code wird jeweils in das Feld Custom Layout Update im Register Design eingegeben. Das eingegebene Layout-XML-Code gilt dann für das bearbeitete Produkt, die Kategorie oder die CMS-Seite.

Zum Bearbeiten des globalen Layouts sollte in Magento die eigene local.xml im Layout-Verzeichnis app/design/frontend/default/mytheme des aktuellen Themes mytheme angelegt und bearbeitet werden. Alle Befehle müssen sich innerhalb des Knotens layout befinden:

<?xml version="1.0"?>
<layout version="0.1.0">

    ...

</layout>

Im Backend der der Layout-XML-Code ohne diesen Knoten verwendet.

In app/design/frontend/base/default/layout/page.xml ist der Block mit dem Namen head definiert und gibt die Inhalte des HTML-Bereichs <head> aus. Mit der Block-Methode addItem() kann diesem Bereich ein JavaScript oder CSS hinzugefügt werden. Die Methode erwartet zwei Parameter: Pfad-Typ und den Dateinamen relativ zum Pfad-Typ.

Vordefinierte Pfad-Typen:

  • js: js/*.js
  • skin_js: skin/*/*.js
  • js_css: js/*.css
  • skin_css: skin/*/*.css

Quelle: Mage_Page_Block_Html_Head::getCssJsHtml()

Es sind noch weitere Alias-Methoden definiert, die jeweils addItem() mit einem bestimmten Pfad-Typ aufrufen. Folgende Alias-Methoden sind in Mage_Page_Block_Html_Head vorhanden:

  • addCss: ruft addItem mit dem type=skin_css
  • addJs: ruft addItem mit dem type=js
  • addCssIe: ruft addItem mit dem type=skin_css und dem parameter IE
  • addJsIe: ruft addItem mit dem type=js und dem parameter IE

Die local.xml, die dem HTML-Bereich <head> custom.css aus dem Verzeichnis skin/frontend/default/mytheme/css hinzufügt, würde wie folgt aussehen:

<?xml version="1.0"?>
<layout version="0.1.0">

    <default>

        <reference name="head">
            <action method="addItem">
                <param1>skin_css</param1>
                <param2>css/custom.css</param2>
            </action>
        </reference>

    </default>

</layout>

Alternativ kann die Alias-Methode addCss verwendet werden:

<?xml version="1.0"?>
<layout version="0.1.0">

    <default>

        <reference name="head">
            <action method="addCss">
                <param1>css/custom.css</param1>
            </action>
        </reference>

    </default>

</layout>

Die beiden Layout-XML-Codes führen zum gleichen Ergebnis; Sie fügen dem HTML-Bereich <head> auf allen von Magento generierten Seiten die folgende Zeile hinzu:

<head>
...
   <link rel="stylesheet" type="text/css" href="http://domain.tld/skin/frontend/default/mytheme/css/custom.css" media="all" />
...
</head>

Die Alias-Methode addCssIe würde diese Zeilen erzeugen:

<head>
...
<!--[if IE]>
<link rel="stylesheet" type="text/css" href="http://domain.tld/skin/frontend/default/mytheme/css/custom.css" media="all" />
<![endif]-->
...
</head>

Layout-Handles definieren den Kontext, in dem die Layout-Befehle gelten sollen. Das in den Beispielen verwendete Layout-Handle default bezieht sich auf alle Seiten. Neben diesem werden die folgende Handles bei Magento mit zusätzlichem Layout-Code erweitert:

  • catalog_product_view – Alle Produkt-Detailseiten
  • catalog_category_default – Alle Kategorien
  • catalog_category_layered – Alle Ankerkategorien
  • cms_index_defaultindex – Startseite

Mit dem folgenden Layout-XML-Code kann allen Produkt-Detailseiten ein JavaScript aus dem Skin-Verzeichnis des aktuellen Themes mytheme hinzugefügt werden:

<?xml version="1.0"?>
<layout version="0.1.0">

    <catalog_product_view>

        <reference name="head">
            <action method="addItem">
                <param1>skin_js</param1>
                <param2>js/custom.js</param2>
            </action>
        </reference>

    </catalog_product_view>

</layout>

Das Ergebnis wäre die folgende Zeile im HTML-Bereich <head> auf jeder Produktseite:

<head>
...
   <link rel="stylesheet" type="text/css" href="http://domain.tld/skin/frontend/default/mytheme/js/custom.js" media="all" />
...
</head>

Nachbesserung der Rundungsfehlerkorrektur in Magento und PayPal

Rounding Error Fix for Magento and PayPal 1.0.4 auf Magento ConnectRounding Error Fix for Magento and PayPal

Nach der Veröffentlichung unserer Erweiterung, die den Rundungsfehler ausschließlich durch bedingte Erhöhung der Rundungsgenauigkeit korrigieren sollte, wurden uns Konstellationen berichtet, in den der Fehler weiterhin auftritt.

In unserer bisherigen Lösung haben wir die Manipulation der Beträge durch Addition oder Subtraktion des Rundungsfehlers vermieden.

Um sicherzustellen, dass die Warenkorbdaten nach PayPal so übertragen werden, dass die Schlüsselbeträge mit den der Bestellung in Magento übereinstimmen, haben wir uns entschieden nun doch die Formulardaten des PayPal-Moduls zu manipulieren!

Wir erweitern die Methode Mage_Paypal_Model_Api_Standard::getStandardCheckoutRequest, die die Formulardaten für PayPal-Zahlungen bereitstellt. Die Schlüsselbeträge sind die Mehrwertsteuer und der Gesamtbetrag. Da die Nettopreise einzelner Artikel und der Versandkosten nicht direkt in der Magento-Bestelung zu sehen sind, wird die Anpassung dieser Werte nicht auffallen.

Für die Anpassung der Formulardaten gehen wir wie folgt vor:

  • Vergleiche den für PayPal vorbereiteten Gesamtwert mit dem Gesamtwert der Bestellung: Beträgt die Differenz der beiden Werte nicht Null, wird der Rundungsfehler korrigiert. Aufgrund der in Magento bereits vorhandener Logik für die Korrektur des Rundungsfehlers, nehmen wir an, dass der gerundete Rundungsfehler +0.01 oder -0.01 betragen kann.
  • Rekonstruiere die Schlüsselwerte des PayPal-Formulars aus den Bestelldaten: Netto-Gesamtwert der Bestellung, Mehrwertsteuerbetrag, Netto-Versandkosten. Zusätzlich werden noch die Brutto-Versandkosten berechnet, um festzustellen, ob hier ein Rundungsfehler vorliegt.
  • Korrigiere den Rundungsfehler durch Anpassung von: Netto-Versandkosten oder des Netto-Preises des letzten Warenkorbartikels oder des Mehrwertsteuerbetrags. Die Anpassung wird im ersten dieser Schlüsselwerte vorgenommen, bei dem ein Rundungsfehler, berechnet im vorigen Schritt, vorliegt. Aus der Magento-eigenen Rundungsfehlerkorrektur ist uns nicht bekannt, wie der Rundungsfehler des letzten Warenkorbartikels verwendet wird. Deshalb erscheint es uns sinnvoll für die Rundungsfehlerkorrektur den Netto-Preis des letzten Warenkorbartikels anzupassen.

Es folgt die Methode, die die Rundungsfehlerkorrektur implementiert:

public function getStandardCheckoutRequest()
{
    // calculate the rounded request total
    $request = parent::getStandardCheckoutRequest();
    $requestBaseGrandTotal = round($request['amount'] + $request['tax'] - $request['discount_amount'], 2);

    // get the rounded order total
    $orderObj = Mage::getSingleton('sales/order')
                ->loadByIncrementId(Mage::getSingleton('checkout/session')->getLastRealOrderId());
    $orderBaseGrandTotal = round($orderObj->getBaseGrandTotal(), 2);

    // get the rounded rounding error
    $roundingError = round($orderBaseGrandTotal - $requestBaseGrandTotal, 2); // -0.01 or +0.01

    // fix the rounding error
    if($roundingError) {
        $order = array(); // create an array from order data resembling the structure or the request array
        $roundingDeltas = array(); // save the rounding error

        $order['amount'] = round($orderObj->getBaseSubtotal() + $orderObj->getBaseShippingAmount(), 2);
        $roundingDeltas['amount'] = ($orderObj->getBaseSubtotal() + $orderObj->getBaseShippingAmount()) - $order['amount'];

        $order['tax'] = round($orderObj->getBaseTaxAmount(), 2);
        $roundingDeltas['tax'] = $orderObj->getBaseTaxAmount() - $order['tax'];

        $order['shipping'] = round($orderObj->getBaseShippingAmount(), 2);
        $roundingDeltas['shipping'] = $orderObj->getBaseShippingAmount() - $order['shipping'];

        // not contained in the request but useful to determine if there is a rounding error in shipping
        $order['shipping_incl_tax'] = round($orderObj->getBaseShippingAmount() + $orderObj->getBaseShippingTaxAmount(), 2);
        $roundingDeltas['shipping_incl_tax'] = ($orderObj->getBaseShippingAmount() + $orderObj->getBaseShippingTaxAmount()) - $order['shipping_incl_tax'];

        $orderTotalItemCount = $orderObj->getTotalItemCount();

        // hide rounding error in shipping
        if($roundingDeltas['shipping_incl_tax'] && $order['shipping'] > 0) {
            if(isset($request['amount_'.($orderTotalItemCount+1)])) { // ensure that the shipping item is there
                $request['amount_'.($orderTotalItemCount+1)] += $roundingError;
            }
            $request['shipping'] += $roundingError;
            $request['amount'] += $roundingError;

        // hide rounding error in the last cart item
        } elseif($roundingDeltas['amount'] && $order['amount'] > 0) {
            if(isset($request['amount_'.($orderTotalItemCount+1)])) {
                $request['amount_'.($orderTotalItemCount+1)] += $roundingError;
            }
            $request['amount'] += $roundingError;
        } else {
            // hide rounding error in tax
            if($order['tax'] > 0) {
                $request['tax'] += $roundingError;
                $request['tax_cart'] += $roundingError;
            } else {
                // do not correct rounding error in this unexpected situation
            }
        }

    }
    return $request;
}

Rounding Error Fix for Magento and PayPal 1.0.4 auf Magento ConnectRounding Error Fix for Magento and PayPal

Behebung des Rundungsfehlers in Magento und PayPal

Rounding Error Fix for Magento and PayPal

Der Rundungsfehler wurde in Magento 1.8 behoben. Bei starker Modifikation durch die vielen Erweiterungen kann ein Update von Magento aufwändig werden, sodass die Rundungsfehlerbehebung mit einer leichtgewichtigen Erweiterung für viele Shop-Betreiber weiterhin interessant bleibt.

Rounding Error Fix for Magento and PayPal auf Magento Connect

Ursprung des Rundungsfehlers

Nach PayPal-Anforderungen müssen alle Preise als kaufmännisch auf zwei Nachkommastellen gerundete Fixpunktzahlen übermittelt werden.

Bei kaufmännischer Rundung wird beispielsweise 1,005 auf 1,01 gerundet und 1,004 auf 1,00. Offensichtlich kann dies zu einem Rundungsfehler führen, je nach dem in welchem Rechenschritt gerundet wird:

  • Rundung der Summe: 1,004 + 1,004 = 2,008 ≈ 2,01
  • Rundung der Summanden: 1,004 ? 1,00; 1,00 + 1,00 = 2,00 ≠ 2,01

Wenn bei PayPal-Zahlungen Warenkorbartikel übermittelt werden, werden ihre Nettopreise und der Mehrwertsteuerbetrag übermittelt, sodass PayPal auf seiner Seite den Brutto-Gesamtbetrag berechnet. Durch die Umrechnung der Bruttopreise kann es zu einem Rundungsfehler kommen:

  • Angenommen der Warenkorb enthält drei verschiedene virtuelle Artikel (d. h. keine Versandkosten).
  • Jeder Artikel kostet brutto 9,99 €, sodass der Brutto-Warenkorbwert 3 * 9,99 € = 29,97 € beträgt.
  • An PayPal werden Nettopreise und der Mehrwertsteuerbetrag übertragen:
    • 29,97 € / 1,19 * 0,19 = 4,7851 € ≈ 4,79
    • 9,99 € / 1,19 = 8,3949 ≈ 8,39 €
    • 3 * 8,39 € + 4,79 € = 29,96 € ≠ 29,97 €
  • Durch den Rundungsfehler ist eine Abweichung um 0,01 € entstanden!

Doch in der Praxis bekommt PayPal die Daten so übermittelt, dass der Brutto-Gesamtbetrag mit dem von Magento bis auf Cent genau übereinstimmt:

Obwohl der Bruttopreis bei allen drei Produkten gleich war, wurde in einem der drei Fälle ein anderer Nettopreis berechnet!

Die Korrektur des Rundungsfehlers findet allerdings nicht im PayPal-Modul statt, sondern bereits im Warenkorb. In der Methode Mage_Tax_Model_Sales_Total_Quote_Subtotal ::_totalBaseCalculation wird der Rundungsfehler auf den Nettopreis des nächsten Produkts in der Liste vor der Rundung aufaddiert wird. Die Rundungsfehler werden im Array _roundingDeltas von _deltaRound verwaltet. Im obigen Beispiel rechnet Magento, reduziert auf das Wesentliche, wie folgt:

  • 1. Artikel: 9,99 / 1,19 = 8,39495798
    • Kaufmännisch gerundet: 8,39
    • Rundungsfehler: 8,39495798 – 8,39 = 0,00495798
  • 2. Artikel: 9,99 / 1,19 + 0,00495798 = 8,39991597
    • Kaufmännisch gerundet: 8,40
    • Rundungsfehler: 8,39991597 – 8,40 = -0,000084034
  • 3. Artikel: 9,99 / 1,19 – 0,000084034 = 8,39487395
    • Kaufmännisch gerundet: 8,39
    • Rundungsfehler: 8,39487395 – 8,39 = 0,00487395

Die Ergebnisse dieser Berechnung lassen sich bereits beim Befüllen des Warenkorbs beobachten. In der Datenbank sind die gerundeten und korrigierten Nettopreise beispielsweise in der Spalte base_price der Tabelle sales_flat_quote_item sichtbar.

Der Ursprung des Rundungsfehlers ist also nicht das PayPal-Modul. Wir haben beobachtet, dass beim Auftreten des Rudnungsfehlers dieser von dem Mehrwertsteuerbetrag ausgeht. Die beschriebene Rudnungsfehlerkorrektur wirkt sich auch auf die Berechnung des Mehrwertsteuerbetrags aus.

Bisherige Lösungsansätze

Im Web gibt es zahlreiche Berichte und Lösungsansätze für die Behebung des Rundungsfehlers. In allen Fällen wird die Rundungsgenauigkeit in Mage_Core_Model_Store::roundPrice von zwei auf vier Nachkommastellen erhöht.

Diese Anpassung verursacht einen Rundungsfehler an anderen Stellen, dem durch weitere Anpassungen entgegengewirkt werden muss. In einigen Lösungen werden Formulardaten des PayPal-Moduls durch Addieren oder Subtrahieren von 0.01 vor dem Absenden manipuliert.

Keines dieser Ansätze behebt den Rundungsfehler vollständig und für alle Konstellationen von Produktpreisen und Steuereinstellungen. Dennoch danken wir allen Authoren, die ihre Lösungen veröffentlicht haben, denn sie haben uns bei der Entwicklung dieser Erweiterung sehr geholfen!

Behebung des Rundungsfehlers mit Xonu_RoundingErrorFix

Auch unsere Erweiterung überschreibt Mage_Core_Model_Store::roundPrice und manipuliert dort die Rundungsgenauigkeit.

Dabei wird abhängig von der aufrufenden Klasse entschieden, auf wieviele Nachkommastellen gerundet wird: Für Aufrufe, die von der Klasse Mage_Tax_Model_Sales_Total_Quote_Subtotal ausgehen wird auf zwei, für alle restlichen Aufrufe auf vier Nachkommastellen genau gerundet.

Wie oben gezeigt, ist diese Klasse in die Berechnung der kaufmännisch gerundeten Nettopreise mit Rundungsfehlerkorrektur involviert. Erhöhung der Rundungsgenauigkeit auf vier Nachkommastellen stört diese Rechenlogik erheblich!

PayPal verwendet für die Verarbeitung der Zahlung die Methode Mage_Sales_Model_Order_Payment::registerCaptureNotification. U. a. hier wird der der „Betrugsverdacht“ erkannt, wenn _isCaptureFinal einen false zurückgibt:

public function registerCaptureNotification($amount)
{
    ....

    if (!$invoice) {
        if ($this->_isCaptureFinal($amount)) {
            $invoice = $order->prepareInvoice()->register();
            $order->addRelatedObject($invoice);
            $this->setCreatedInvoice($invoice);
        } else {
            $this->setIsFraudDetected(true);
            $this->_updateTotals(array('base_amount_paid_online' => $amount));
        }
    }

    ....
}

In Mage_Sales_Model_Order_Payment::_isCaptureFinal wird der über PayPal gezahlter Betrag, übermittelt in der IPN, mit dem Gesamtbetrag der Bestellung verglichen:

protected function _isCaptureFinal($amountToCapture)
{
    $amountToCapture = $this->_formatAmount($amountToCapture, true);
    $orderGrandTotal = $this->_formatAmount($this->getOrder()->getBaseGrandTotal(), true);
    if ($orderGrandTotal == $this->_formatAmount($this->getBaseAmountPaid(), true) + $amountToCapture) {
        if (false !== $this->getShouldCloseParentTransaction()) {
            $this->setShouldCloseParentTransaction(true);
        }
        return true;
    }
    return false;
}

Dabei wird die Methode Mage_Sales_Model_Order_Payment::_formatAmount verwendet, sodass wenn roundPrice auf vier statt zwei Nachkommastellen rundet, die Beträge nicht übereinstimmen könnten, weil der von PayPal übermittelte Betrag immer auf zwei Nachkommastellen gerundet ist:

protected function _formatAmount($amount, $asFloat = false)
{
     $amount = Mage::app()->getStore()->roundPrice($amount);
     return !$asFloat ? (string)$amount : $amount;
}

Die Erweiterung überschreibt auch diese Methode, sodass sie auf zwei Nachkommastellen genau rundet:

protected function _formatAmount($amount, $asFloat = false)
{
    $amount = round($amount,2);
    return !$asFloat ? (string)$amount : $amount;
}

Eine weitere Methode im PayPal-Modul scheitert an der Erhöhung der Nachkommastellen in roundPrice: Mage_Paypal_Model_Api_Abstract::_filterAmount und liefert in bestimmten Fällen ein Ergebnis, welches wiederum einen Rundungsfehler verursacht:

protected function _filterAmount($value)
{
    return sprintf('%.2F', $value);
}

Auch diese Methode überschreiben wir:

protected function _filterAmount($value)
{
    return sprintf('%.2F', round($value, 2));
}

In dem Update der Erweiterung werden zusätzlich noch die PayPal-Formulardaten vor der Übermittlung mit den Bestelldaten verglichen. Bei einer Abweichung wird der Rundungsfehler durch gezielte Anpassung der Formulardaten korrigiert.

Rounding Error Fix for Magento and PayPal

Rounding Error Fix for Magento and PayPal auf Magento Connect

SSL in der XAMPP-Konfiguration von Apache aktivieren

Bei Entwicklungsarbeiten rund um die Umschaltung von http:// auf https:// in Magento sollte in der lokalen Entwicklungsumgebung SSL aktiviert werden. In XAMPP-Konfiguration von Apache ist SSL als Voreinstellung deaktiviert.

Davon ausgehend, dass virtuelle Hosts und lokale Domains verwendet werden, soll diese Anleitung bei der Aktivierung von SSL unterstützen.

Einrichtung virtueller Domains

Lokale Domains werden in der hosts-Datei verwaltet. Diese Datei befindet sich unter Windows unter:

%SystemRoot%\system32\drivers\etc\hosts

Die hosts-Datei könnte die folgende Zeile enthalten:

127.0.0.1           local.xonu.de

Zum Bearbeiten der hosts-Datei muss der verwendete Texteditor als Administrator gestartet werden!

Einrichtung des virtuellen Hosts

In der XAMPP-Konfiguration von Apache werden virtuelle Hosts in der folgenden Datei verwaltet:

xampp\apache\conf\extra\httpd-vhosts.conf

Überprüfen Sie, dass die Einbettung von httpd-vhosts.conf in der Hauptkonfigurationsdatei httpd.conf (im Verzeichnis xampp\apache\conf) enthalten und aktiv ist, d. h. nicht mit Raute am Zeilenanfang auskommentiert wurde:

Include "conf/extra/httpd-vhosts.conf"

Die Konfiguration des virtuellen Hosts für unverschlüsselte Verbindungen könnte wie folgt aussehen:

<VirtualHost *:80>
    DocumentRoot "/htdocs/local.xonu.de/httpdocs"
    ServerName local.xonu.de
    ErrorLog "logs/xonu-error.log"
    ErrorLog "logs/xonu-custom.log"
</VirtualHost>

Für verschlüsselte Verbindungen muss ein weiterer Block für den Port 443 eingefügt werden:

<VirtualHost *:443>
    DocumentRoot "/htdocs/local.xonu.de/httpdocs"
    ServerName local.xonu.de
    SSLEngine on
    SSLCertificateFile "conf/ssl.crt/server.crt"
    SSLCertificateKeyFile "conf/ssl.key/server.key"
</VirtualHost>

Wie auch das Log-Verzeichnis, wird der Pfad zu den Zertifikat-Dateien relativ zum Hauptverzeichnis von Apache \xampp\apache angegeben. Diese Dateien sind in XAMPP bereits enthalten.

In früheren Versionen von Apache musste noch in xampp\apache\conf\extra\httpd-vhosts.conf nach NameVirtualHost *:80 die neue Zeile mit NameVirtualHost *:443 eingefügt werden. In der aktuellen und zukünftigen Versionen ist dies nicht mehr nötig.

Beim Start von Apache im XAMPP Control Panel könnten folgende Fehler gemeldet werden:

[Apache]     Error: Apache shutdown unexpectedly.
[Apache]     This may be due to a blocked port, missing dependencies,
[Apache]     improper privileges, a crash, or a shutdown by another method.
[Apache]     Check the "/xampp/apache/logs/error.log" file
[Apache]     and the Windows Event Viewer for more clues

In diesem Fall muss noch das SSL-Modul von Apache aktiviert werden. Dazu bitte in httpd.conf die folgende Zeile mit Strg+F finden und die Kommentar-Raute entfernen:

LoadModule ssl_module modules/mod_ssl.so

Sichere Verbindung für ein virtuelles Magento-Verzeichnis erzwingen

Als Nachtrag zum Artikel über Router und Controller in Magento, kann das Laden eines Controllers über eine verschlüsselte Verbindung (sofern konfiguriert) erzwungen werden, sodass von http auf https automatisch weitergeleitet wird:

<config>
<!-- ... -->
    <frontend>
        <routers>
            <eindeutiger_router_name>
                <use>standard</use>
                <args>
                    <module>Xonu_Test</module>
                    <frontName>start</frontName>
                </args>
            </eindeutiger_router_name>
        </routers>

        <!-- Verschlüsselte Verbindung erzwingen: -->
        <secure_url>
            <eindeutiger_url_name>/start</eindeutiger_url_name>
        </secure_url>
    </frontend>
</config>

Mit dieser Konfiguration leitet http://local.xonu.de/start automatisch auf https://local.xonu.de/start weiter.

Magento 1.8: Rundungsfehler behoben!

Seit dem 25. Oktober kann die finale Version von Magento 1.8 (Community Edition) heruntergeladen werden.

Unter den Neuerungen in Magento 1.8 wird die Behebung des Rundungsfehlers zurecht im ersten Punkt unter den Highlights erwähnt.

Der Rundungsfehler in früheren Versionen war inzwischen so bekannt geworden, dass Trusted Shops bei der Abnahme absichtlich einen Warenkorb mit Rundungsfehler zu kombiniert versucht. Die Abnahme wird verweigert bis der Rundungsfehler behoben wurde.

Der bereits im Warenkorb sichtbare Rundungsfehler pflanzt sich konsequent fort bis zur Erfassung der Bestellung im Backend:

Mit 14,99 EUR kommt Magento 1.8 zum richtigen Ergebnis!

Auf Magento Connect haben wir eine Erweiterung veröffentlicht, die den Rundungsfehler in Magento 1.7 und PayPal behebt: Rounding Error Fix for Magento and PayPal

Eigenen Code in einem beliebigen Unterverzeichnis von Magento ausführen

Eine beliebte Frage in einer Magento-Schulung für Entwickler: Wie kann unter einer frei gewählten URL in Magento eigener PHP-Code ausgeführt werden?

Die Magento-Installation ist z. B. unter http://domain.de/shop/ erreichbar. Unter http://domain.de/shop/beliebiges-unterverzeichnis/landingpage.html soll ein PHP-Code ausgeführt werden und „Hallo Welt!“ ausgeben.

Eigenes virtuelles Unterverzeichnis in Magento

Der Router bestimmt das (virtuelle) Unterverzeichnis, bei dessen Aufruf der damit verknüpfte Controller ausgeführt wird.

Hierfür muss eine Magento-Erweiterung mit drei Dateien erstellt werden, damit wir beim Aufruf des Verzeichnisses http://127.0.0.1/custom/ unseren eigenen Code ausführen können:

app/etc/modules/Xonu_Controller.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Xonu_Controller>
            <active>true</active>
            <codePool>local</codePool>
        </Xonu_Controller>
    </modules>
</config>

Durch diese Konfiguration wird der Code der Erweiterung im Verzeichnis app/code/<codePool>/<Namespace>/<Name>, also app/code/app/code/local/Xonu/Controller erwartet. Die Namen Xonu und Controller sind frei gewählt.

Um Fehler zu vermeiden, sollten der Namensraum und der Name der Erweiterung stets nur einen Großbuchstaben am Anfang enthalten (z. B. statt MyController, sollte Mycontroller verwendet werden).

app/code/local/Xonu/Controller/etc/config.xml

<?xml version="1.0"?>
<config>
    <global> <!-- Der Router wird im Frontend und Backend berücksichtigt. -->
    </global>

    <admin> <!-- Der Router wird im Frontend und Backend berücksichtigt. -->
    </admin>

    <frontend> <!-- Der Router wird nur im Frontend berücksichtigt. -->
        <routers>
            <xonu_controller_router> <!-- Frei gewählter Name des XML-Knotens. -->
                <use>standard</use>
                <args>
                    <!-- Hauptverzeichnis/Klassenprefix der Erweiterung. -->
                    <module>Xonu_Controller</module>
                    <!-- Frei gewählter Name für das erste virtuelle Unteverzeichnis. -->
                    <frontName>custom</frontName>
                </args>
            </xonu_controller_router>
        </routers>
    </frontend>
</config>

app/code/local/Xonu/Controller/controllers/IndexController.php

<?php

class Xonu_Controller_IndexController extends Mage_Core_Controller_Front_Action {

    public function indexAction() {
        echo "Hallo Welt!";
    }
}

Die Benennungen des Unterverzeichnisses controllers, klein geschrieben, der Datei IndexController.php und des darin definierten Klassennamens Xonu_Controller_IndexController und ihre Ableitung von einer bestimmten Core-Klasse ist von Magento vorgeschrieben.

Nach dem der Konfigurationscache aktualisiert wurde, falls aktiv, wird bereits das registrierte Unterverzeichnis funktionieren:

http://127.0.0.1/custom

Hallo Welt!

Gleiches gilt für die virtuelle Unterverzeichnisse /custom/index und /custom/index/index. Das erste Unterverzeichnis index bezieht sich auf den Namen des Controllers IndexController.php. Das zweite Unterverzeichnis index bezieht sich auf den Präfix im Namen der darin definierten Methode indexAction(). Für die folgenden Aufrufe bekommen wir also die gleiche Ausgabe:

http://127.0.0.1/custom/index
http://127.0.0.1/custom/index/index

Durch Einführung einer weiteren Methode mit einem beliebigen Präfix, gefolgt vom Schlüsselnamen Action, können wir weitere virtuelle Unterverzeichnisse für die URL http://127.0.0.1/custom/index definieren:

app/code/local/Xonu/Controller/controllers/IndexController.php

<?php

class Xonu_Controller_IndexController extends Mage_Core_Controller_Front_Action {

    public function indexAction() {
        echo "Hallo Welt!";
    }

    public function anotherAction() {
        echo "Xonu_Controller_IndexController::anotherAction";
    }

}

Die zweite Methode anotherAction() kann durch die URL http://127.0.0.1/custom/index/another aufgerufen werden und liefert die folgende Ausgabe:

http://127.0.0.1/custom/index/another

Xonu_Controller_IndexController::anotherAction

Das erste Unterverzeichnis wird durch den Präfix im Namen der Controller-Klasse und der php-Datei bestimmt. Weiteres Unterverzeichnis /custom/second können wir durch die Definition einer weiteren Controller-Klasse registrieren:

app/code/local/Xonu/Controller/controllers/SecondController.php

<?php

class Xonu_Controller_SecondController extends Mage_Core_Controller_Front_Action {

    public function indexAction() {
        echo "Xonu_Controller_SecondController::indexAction";
    }

    public function anotherAction() {
        echo "Xonu_Controller_SecondController::anotherAction";
    }

}

Nun bekommen wir folgende Ausgaben:

http://127.0.0.1/custom/second
http://127.0.0.1/custom/second/index

Xonu_Controller_SecondController::indexAction

Noch tiefere Unterverzeichnisse können als Parameter über spezielle Methoden von Magento ausgelesen werden:

app/code/local/Xonu/Controller/controllers/DeeplinkController.php

<?php

class Xonu_Controller_DeeplinkController extends Mage_Core_Controller_Front_Action {

    public function evaluateAction() {
        echo '<pre>';
        echo "Xonu_Controller_DeeplinkController::indexAction\n";

        echo "\n".'$_GET:'."\n";
        print_r($_GET);

        echo "\n".'Mage::app()->getRequest()->getParams():'."\n";
        print_r(Mage::app()->getRequest()->getParams());
    }
}

Beispielaufrufe und -Ausgaben:

http://127.0.0.1/custom/deeplink/evaluate/param1/value1/param2/value2

Xonu_Controller_DeeplinkController::indexAction

$_GET:
Array
(
)

Mage::app()->getRequest()->getParams():
Array
(
    [param1] => value1
    [param2] => value2
)

GET-Parameter können direkt über die GET-Variable als auch über die Methoden von Magento ausgelesen werden. Letzteres ist durch interne Filter sicherer und deshalb der Arbeit direkt mit der GET-Variable vorzuziehen:

http://127.0.0.1/custom/deeplink/evaluate/param1/value1/?param2=value2

Xonu_Controller_DeeplinkController::indexAction

$_GET:
Array
(
    [param2] => value2
)

Mage::app()->getRequest()->getParams():
Array
(
    [param1] => value1
    [param2] => value2
)

http://127.0.0.1/custom/deeplink/evaluate/?param1=value1&param2=value2

Xonu_Controller_DeeplinkController::indexAction

$_GET:
Array
(
    [param1] => value1
    [param2] => value2
)

Mage::app()->getRequest()->getParams():
Array
(
    [param1] => value1
    [param2] => value2
)

Das eigene Verzeichnis beliebig gestalten

Bei der Gestaltung virtueller Unterverzeichnisse ist man also an bestimmte Regeln von Magento gebunden. Durch das URL Rewrite Verwaltung lassen sich diese Verzeichnisstrukturen hinter einer beliebigen URL verbergen.

Im Backend, unter Katalog > URL Rewrite Verwaltung legen wir einen neuen URL Rewrite an. Im obersten Menü wählen wir Benutzerdefiniert aus und füllen das Formular aus:

Magento URL Rewrite Management

  • Store: Hier ist das StoreView gemeint, in dem die URL gelten soll. Es gibt keine Möglichkeit die URL mit einem Eintrag für alle StoreViews festzulegen; in diesem Fall muss die URL für jeden StoreView einzeln angelegt werden. Beachten Sie auch, dass die Spalte store_id in der Tabelle core_url_rewrite, wo die URL Rewrites verwaltet werden, niemals die 0 enthält.
  • ID Pfad: Neben der automatisch vergebenen Id, muss dem Rewrite eine zusätzliche interne Id vergeben werden. Dies kann ein beliebiger eindeutiger Wert sein und wird in der Spalte id_path gespeichert.
  • Anfragepfad: Das ist die URL, die frei gestaltet werden kann. Sie kann z.B. beliebige virtuelle Unterverzeichnisse enthalten. Die URL wird ohne führenden Slash und ohne Domainnamen eingegeben. Für die URL http://domain.de/beliebiges-unterverzeichnis/landingpage.html, wird als Anfragepfad beliebiges-unterverzeichnis/landingpage.html angegeben. Befindet sich die Magento-Installation in einem Unterverzeichnis, z.B. http://domain.de/shop, enthält der Anfragepfad dieses Unterverzeichnis nicht.
  • Zielpfad: Dies ist das Verzeichnis unter dem ein Controller aufgerufen wird, welches an bestimmte Vorgaben geknüpft ist und deshalb nicht ganz frei gestaltet werden kann. Auch dieses Verzeichnis wird, wie Anfragepfad, ohne führenden Slash und ohne Domain (und Shop-Verzeichnis) eingetragen: custom/deeplink/evaluate/param1/value1/param2/value2.
  • Weiterleitung: Für unseren Zweck ist Nein die optimale Einstellung. Wird die Weiterleitung aktiviert, wird der Nutzer zu dem Zielpfad weitergeleitet und sieht ihn in der Adressleiste des Browsers.

Nach der Speicherung des Rewrites bekommen wir unter der neuen URL die folgende Ausgabe:

http://127.0.0.1/beliebiges-unterverzeichnis/landingpage.html

Xonu_Controller_DeeplinkController::indexAction

$_GET:
Array
(
)

Mage::app()->getRequest()->getParams():
Array
(
    [param1] => value1
    [param2] => value2
)

Wir können weitere GET-Parameter übergeben, doch diese können ausschließlich über die GET-Variable ausgelesen werden:

http://127.0.0.1/beliebiges-unterverzeichnis/landingpage.html?param3=value3

Xonu_Controller_DeeplinkController::indexAction

$_GET:
Array
(
    [param2] => value3
)

Mage::app()->getRequest()->getParams():
Array
(
    [param1] => value1
    [param2] => value2
)

XML, XPath, PHP: DOMXPath::query() funktioniert nicht?

Möchte man XML-Daten dynamisch einlesen und den Code möglichst generisch gestalten, ist DOMXPath::query() sehr hilfreich: Die Pfade können in einer separaten Datenquelle als Zeichenketten hinterlegt werden.

Liest man Tutorials auf php.net und in anderen Blogs, bleibt oft ein für die Praxis wichtiges Detail unerwähnt. Es folgt ein kurzes Beispiel einer XML-Antwort:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company xmlns:xlink="http://www.w3.org/1999/xlink"
         xmlns="http://api.xonu.de" id="123">
    <name>xonu</name>
</company>

Das Auslesen der id über XPath funktioniert problemlos:

$xpathStr = '@id';

// $xmlStr <-- XML-Antwort
$domDoc = new DOMDocument();
$domDoc->loadXML($xmlStr);
$domXpath = new DOMXPath($domDoc);

$domNode = $domXpath->query($xpathStr);

if($domNode->length) {
    print $domNode->item(0)->nodeValue;
} else {
    print 'Nichts gefunden.';
}

Die Ausgabe dieses Skripts ist der Wert des id-Attributes des Wurzelknotens: 123. Nun wollen wir den Wert des Unterknotens name auslesen:

$xpathStr = 'name';

Doch das funktioniert nicht, die Ausgabe ist „Nichts gefunden.“. Verschiedene äquivalente Formulierungen des Pfades für das id-Attribut funktionieren, aber nicht für den Unterknoten name:

$xpathStr = '@id'; // Ausgabe: 123
$xpathStr = '//@id'; // Ausgabe: 123
$xpathStr = '/*/@id'; // Ausgabe: 123
$xpathStr = 'name'; // Ausgabe: Nichts gefunden.
$xpathStr = '//name'; // Ausgabe: Nichts gefunden.
$xpathStr = '/*/name'; // Ausgabe: Nichts gefunden.

Durch Entfernen des xmlns-Attributs kommt man dem Problem auf die Spur: Dieses Attribut fehlt oft in den Tutorial-Beispielen, ist aber in der Praxis meistens vorhanden! Nach dem Entfernen von xmlns=“http://api.xonu.de“ aus der XML-Antwort, funktionieren alle oben aufgelisteten X-Pfade.

Der in xmlns angegebener XML-Namespace, also der Namensraum, muss explizit registriert werden:

$xmlNs = $domDoc->documentElement->lookupnamespaceURI(null);
$domXpath->registerNamespace('x', $xmlNs);

Jetzt können wir den Unterknoten name über die folgenden X-Pfade abfragen:

$xpathStr = 'x:name';
$xpathStr = '//x:name';
$xpathStr = '/*/x:name';

Es könnten allerdings bereits hunderte X-Pfade definiert worden sein (z. B. im Profil eines XML-Adapters), die nun alle überarbeitet werden müssten, um sich auf einen bestimmten Namensraum zu beziehen. Um dies zu vermeiden, kann das xmlns-Attribut programmatisch gelöscht werden:

$xmlNs = $domDoc->documentElement->lookupnamespaceURI(null);
// $domXpath->registerNamespace('x', $xmlNs);
$domDoc->documentElement->removeAttributeNS($xmlNs, '');

Nun würden alle X-Pfade ohne eines expliziten Bezugs auf einen Namensraum funktionieren.

Beispielcode zum Ausprobieren herunterladen (domxpath-example.php)

Magento-Konfiguration als XML-Baum anzeigen, debuggen, analysieren

Magento fügt alle XML-Konfigurationsdateien aus den Verzeichnissen app/etc, app/etc/modules und config.xml aus den etc-Verzeichnissen einzelner Module zu einem einzigen XML-Baum zusammen. Um Magento besser zu verstehen und bei der Entwicklung eigener Module kann es hilfreich sein, einen Einblick in diesen Magento-Konfigurationsbaum zu bekommen.

getXmlString() als Methode von getConfig() gibt eine speichersparende Ausgabe ohne Einschübe und Zeilenumbrüche. Sie ist für Entwicklungsumgebungen geeignet, die XML visualisieren können (z. B.  in Eclipse: Strg+O in der Code-Ansicht oder die Design-Ansicht):

$configXML = Mage::app()->getConfig()->getXmlString();

asNiceXml() gibt geordneten XML-Code mit Zeilenumbrüchen und hierarchischen Einschüben aus:

$configXML = Mage::getConfig()->getNode()->asNiceXml();

Wenn der XML-Konfigurationsbaum auf einen bestimmten XPath reduziert werden soll, kann dieser als String-Parameter von getNode() übergeben werden. Der nachfolgende Beispiel listet alle registrierten Helper auf:

$configXML = Mage::getConfig()->getNode('global/helpers')->asNiceXml();

Die Umfangreiche Ausgabe kann in einer Datei gespeichert werden. Der nachfolgende Code speichert die XML-Ausgabe im Log-Verzeichnis von Magento:

file_put_contents(Mage::getBaseDir('log').DS.'magento_config.xml', $configXML);

 

Hauptknoten des Magento-Konfigurationsbaums

  • config
    • modules – Modulkonfiguration (codePool, active, depends)
    • global – Definitionen, die global gelten (models, resources, blocks, helpers, events, fieldsets, template, cache…)
    • frontend – Definitionen, die nur im Frontend gelten (events, secure_url, routers, translate, layout)
    • adminhtml – Definitionen, die nur im Backend gelten (events, global_search, translate, layout)
    • default – Konfiguration für alle Store Views, Voreinstellungen
    • stores – Konfiguration für die Store Views (default, admin, [store_code])
    • websites – Konfiguration für die Websites
    • admin – Backend-Router und Fieldsets (routers, fieldsets, attributes)
    • install – Magento-Installation (nicht Modul-Installation)
    • crontab – Cronjobs innerhalb des Magento-Systems
    • varien – Altlasten von Varien
    • phoenix – Spezielle Definitionen für MoneyBookers
    • [custom] – Spezielle Definitionen für eigene Module möglich

Postfix-Konfiguration: www-data im Return-Path ändern

Im Quellecode einer Email gibt es deutlich mehr Informationen als das Email-Programm dem Nutzer anzeigt. Eines dieser verborgenen Informationen ist der Return-Path, der von Spam-Filtern gerne ausgewertet wird. Passt dieser nicht, wird die Email in den Spam-Ordner verschoben.

Auch kann es beim Zielserver dazu führen, dass er die Email mit dem „falschen“ Return-Path mit der Fehlermeldung „501: Sender address must contain a domain“ gar nicht annimmt.

In der Voreinstellung eines Debian/Ubuntu Servers wird als Return-Path www-data@xonu.de verwendet. Im Magento-Shop wird meistens eine andere Email-Adresse als Absender eingetragen, z.B. bestellungen@mein-shop.de. Die Tatsache, dass der Return-Path-Wert sich von from From-Wert unterscheidet, kann dazu führen, dass Emails in den Spam-Ordner verschoben werden.

Durch Anpassung der Postfix-Konfiguration auf dem Server kann der Return-Path geändert werden:

1. Im Verzeichnis /etc/postfix/ erstellen Sie die Datei mit dem Namen canonical und folgendem Inhalt:

www-data   bestellungen@mein-shop.de

2. Führen Sie auf der Kommandozeile den folgenden Befehl aus:

postmap hash:/etc/postfix/canonical

Überprüfen Sie, dass im Verzeichnis /etc/postfix/ die Datei canonical.db erstellt wurde.

3. Fügen Sie der Postfix-Konfigurationsdatei /etc/postfix/main.cf folgende Zeile hinzu:

canonical_maps = hash:/etc/postfix/canonical

Beachten Sie, hier wird die Datei ohne Erweiterung angegeben. Postfix sucht beim Start automatisch die Datei mit der Erweiterung .db.

4. Starten Sie Postfix neu:

/etc/init.d/postfix restart

Schicken Sie eine Test-Email (Bestellung aus dem Backend manuell versenden) und überprüfen Sie den Return-Path im Quellcode der Email:

Der obige Screenshot zeigt die Ecke des Email-Fensters von Thunderbird, daneben einen Auszug aus dem Email-Quelltext: Der From-Wert gleicht dem Wert von Return-Path.

Optimierung der Button-Lösung von German Setup

Entsprechend der Button-Lösung, ändert German Setup den letzten Schritt im Checkout so ab, dass die Häckchen für Bestellbedingungen (AGB und Widerrufbelehrung) oberhalb der Tabelle mit Warenkorbwaren angezeigt werden. Neben jeder Bestellbedingung wird dabei der Link [Anzeigen] in eckigen Klammern angezeigt.

Die aktuelle Button-Lösung von German Setup ist in zwei Bereichen verbesserungswürdig:

  • Die Bestellbedingungen sollen über den Link [Anzeigen] nicht nur mit Linksklick, sondern mit der mittleren Maustaste oder über das Browser-Kontextmenü gezielt im neuen Tab geöffnet werden können.
  • Der Link sollte in die Checkbox-Beschriftung integriert werden:

Absichtliches Öffnen im neuen Tab oder Fenster

Um sicher zu gehen, dass das Shop-Fenster geöffnet bleibt, versuchen einige Kunden die Bestellbedingungen mit der mittleren Maustaste oder über das Browser-Kontextmenü (Rechtsklick) gezielt im neuen Fenster zu öffnen, weil aus der Gestaltung des Links [Anzeigen] nicht zu erkennen ist, dass er bereits ein neues Fenster öffnet.

Mit dem JavaScript-Befehl window.Open wird nach dem Klick ein neues Fenster öffnet (OnClick-Ereignis), in dem die Inhalte der Bestellbedingungen angezeigt werden:

Der href-Parameter des Links ist auf „#“ gesetzt. Der Nachteil dieser Lösung ist, dass wenn ein Nutzer versucht, das Fenster absichtlich in einem neuen Tab zu öffnen, ob über das Browser-Kontextmenü oder durch den Klick mit der mittleren Maustaste bzw. mit dem  Mausrad, öffnet sich nicht der Inhalt der Bestellbedingung, sondern (verwirrenderweise) der erste Schritt des Checkouts.

Durch eine einfache Anpassung des Links kann dieses Problem behoben werden: Das target-Attribut des Links wird dem Fensternamen (zweites Parameter von window.Open) gesetzt. Das href-Attribut des a-Tags wird dem URL-Ziel des Fensters gleichgesetzt.

Vereinfacht, ist der Original-Link wie folgt aufgebaut:

<a href="#"
   OnClick="window.open(
     '{Ziel-URL}',
     '',
     '{Fensterparameter}')">
[Anzeigen]</a>

Damit das Öffnen im Neuen Tab funktioniert, muss der Link wie folgt umgestaltet werden:

<a href="{Ziel-URL}"
   target="{Fenster-ID}"
   OnClick="window.open(
     '{Ziel-URL}'),
     '{Fenster-ID}',
     '{Fensterparameter}'">
[Anzeigen]</a>

Beim Auslösen des OnClick-Ereignisses, öffnet das sich wie gewohnt das Fenster. Beim Klick mit der mittleren Maustaste zum Öffnen des Links im neuen Fenster, öffnet sich so tatsächlich ein neues Browser-Tab mit den erwarteten Inhalten der Bestellbedingung.

Zum Realisieren dieser Optimierung, kopieren Sie die folgende Datei unter Beibehaltung der Verzeichnisstruktur in Ihr Theme-Verzeichnis (mehr zum Erstellen und Anpassen von Magento-Themes):

app/design/frontend/base/default/germansetup/checkout/onepage/agreements.phtml

Und ersetzen Sie die Zeile ~52 mit dem modifizierten Link:

<a
   href="<?php echo $this->getUrl('germansetup/frontend/agreements', array('id' => $_a->getId())) ?>"
   target="agreement-<?php echo $_a->getId()?>"
   onclick="window.open(
            '<?php echo $this->getUrl('germansetup/frontend/agreements', array('id' => $_a->getId())) ?>',
            'agreement-<?php echo $_a->getId()?>',
            'width=600,height=600,left=0,top=0,location=no,status=yes,scrollbars=yes,resizable=yes').focus(); return false;">
   <?php echo $this->__('[Show]') ?>
</a>

Integration in die Checkbox-Beschriftung

Der Link [Anzeigen] ist zwar eine sichere und einfache Lösung, bringt aber keine schöne Optik mit sich. Als eine optisch schönere Lösung wäre die Integration des Links zur Anzeige von Inhalten einzelner Bestellbedingungen direkt in die Checkbox-Überschrift, sodass beispielsweise „Allgemeine Geschäftsbedingungen“ zum Link wird:

Die zusätzliche Anforderung wäre die weiterhin einfache Pflege des Inhalts der Checkbox-Überschrift ganz ohne HTML-Code: Der zu verlinkende Bereich des Texts wird mit eckigen Klammern markiert:

D. h. aus „Ich habe die Allgemeinen Geschäftsbedingungen gelesen und stimme diesen ausdrücklich zu.“ wird „Ich habe die [Allgemeinen Geschäftsbedingungen] gelesen und stimme diesen ausdrücklich zu.“

Der einfachheit halber wird dazu die gleiche Datei (als Kopie im eigenen Theme-Verzeichnis) wie im vorherigen Optimierungsvorschlag modifiziert:

app/design/frontend/base/default/germansetup/checkout/onepage/agreements.phtml

Die neue Datei sieht wie folgt aus (sie enthält die vorher beschriebene Optimierung ebenfalls):

<?php
$helper = $this->helper('germansetup');
?>

<?php if (!$this->getAgreements()) return; ?>

<form action="" id="checkout-agreements" onsubmit="return false;">
<ol>
<?php foreach ($this->getAgreements() as $_a): ?>
    <?php
        $checkBoxLabel = $_a->getIsHtml() ? $_a->getCheckboxText() : $this->htmlEscape($_a->getCheckboxText());
        $matches = array();
        if(preg_match('/\[([^\]]*)\]/i', $checkBoxLabel, $matches)) {
            $linkMarkup = $matches[0];
            $linkLabel  = $matches[1];
            $clickableLink = '<a href="'.$this->getUrl('germansetup/frontend/agreements', array('id' => $_a->getId())).'" target="agreement-'.$_a->getId().'" onclick="window.open(\''.$this->getUrl('germansetup/frontend/agreements', array('id' => $_a->getId())).'\', \'agreement-'.$_a->getId().'\', \'width=600,height=600,left=0,top=0,location=no,status=yes,scrollbars=yes,resizable=yes\').focus(); return false;">'.$linkLabel.'</a>';
            $checkBoxLabel = str_replace($linkMarkup, $clickableLink, $checkBoxLabel);
        }
    ?>
    <li>
        <p>
            <input type="checkbox" id="agreement-<?php echo $_a->getId()?>" name="agreement[<?php echo $_a->getId()?>]" value="1" title="<?php echo $this->htmlEscape($_a->getCheckboxText()) ?>" />
            <label for="agreement-<?php echo $_a->getId()?>"><?php echo $checkBoxLabel; ?></label>
        </p>
    </li>
<?php endforeach ?>
</ol>
</form>

Der reguläre Ausdruck \[([^\]]*)\] extrahiert den Text in der eckigen Klammer, mit und ohne Klammer verteilt auf zwei Gruppen $matches[0] und $matches[1], und ersetzt ihn durch den a-Tag mit der gleichen sichbaren Bezeichnung, die als zweite Gruppe $matches[1] ohne der eckigen Klammer extrahiert wurde.

 

Weiterlesen:

Symbolische Links funktionieren nicht, .htaccess funktioniert nicht

Fehlerbeschreibung

Die Startseite von Magento lässt sich laden, aber viele oder alle Unterseiten, darunter Produkt- und Kategorie-Links, führen zu einem Fehler.

Ursachen und Lösungen

Magento-Testumgebung mit Beispieldaten

  • Wenn Sie eine Magento-Testumgebung mit Beispieldaten einrichten, werden die Produkt- und Kategorie-Links nicht sofort funktionieren. Das Problem kann durch Neuindizierung behoben werden. Gehen Sie zu System > Index-Verwaltung und Klicken Sie dort auf Neuaufbau in der Zeile „Katalog URL Rewrites“. Danach sollten alle Links im Magento-Shop mit Beispieldaten funktionieren.

Typische Flüchtigkeitsfehler

  • Ist die .htaccess im Hauptverzeichnis von Magento vorhanden? Hin und wieder gibt es Situationen, in den versteckte Dateien beim Komprimieren des Verzeichnisses nicht berücksichtigt werden. Folglich sind sie nach dem Enpacken nicht vorhanden.
  • Wurde die .htaccess umbenannt? Die Datei muss mit einem Punkt beginnen und in Kleinbuchstaben geschrieben sein.

Konfiguration von Apache

Nach dem Umzug in eine neue Serverumgebung kann es sein, dass die symbolische Links von Magento nicht mehr funktionieren. Dies hängt oft mit der Konfiguration von Apache zusammen.

In der Standardkonfiguration von Apache in Ubuntu Server 10.04 LTS ist die Unterstützung von .htaccess über AllowOverride None deaktiviert. Zusätzlich ist das Apache-Modul mod_rewrite standardmäßig nicht geladen.

  • Ist die Verwendung von .htaccess erlaubt? Bei Ubuntu Server 10.04 LTS befindet sich die betroffene Konfigurationsdatei von Apache unter /etc/apache2/sites-available/default:
<Directory />
    Options FollowSymLinks
    AllowOverride None
</Directory>
<Directory /var/www/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Order allow,deny
    allow from all
</Directory>

Vorausgesetzt Ihre Magento-Installation befindet sich im Verzeichnis /var/www/ (oder einem Unterverzeichnis), wird die Nutzung von .htaccess mit dem zweiten Befehl AllowOverride None verboten. Ändern Sie die entsprechende Zeile zu AllowOverride All:

<Directory />
    Options FollowSymLinks
    AllowOverride None
</Directory>
<Directory /var/www/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    allow from all
</Directory>

Starten Sie Apache z. B. mit /etc/init.d/apache2 restart neu, um die geänderte Konfiguration zu laden.

  • Ist der Apache-Modul rewrite aktiviert? In diesem Fall sind weitere Informationen in der Ausgabe von phpinfo() auffindbar: STRG+F nach mod_rewrite. Versuchen Sie andernfalls das Modul mit a2enmod zu aktivieren und Apache neu zustarten:
a2enmod rewrite
/etc/init.d/apache2 restart

Neue Version von Fixed Gross Price bietet höhere Kompatibilität

Fixed Gross Price auf Magento Connect

Die Verbesserungsvorschläge, die von Entwicklern und Shop-Betreibern mir zugeschickt wurden, haben wir in die neue Version von Fixed Gross Price einfließen lassen.

Die neue Version fixiert nun den Bruttopreis auch bei Bestellungen, die im Backend erstellt werden, vielen Dank an Matthias.

Die Erweiterung ist nun auch kompatibel mit Magento 1.5, wo die Methode hasQuote() noch nicht deffiniert war, vielen Dank an Benjamin für den Hinweis.

Bei einer bestimmten Shop-Konfiguration wurde der Bruttopreis im Katalog nicht fixiert, wenn der Kunde eingeloggt war. Dieses Problem wurde nun ebenfalls behoben.

Die neue Version kann nun von Magento Connect installiert werden.

Weiterlesen: Zum ursprünglichen Artikel über Fixed Gross Price.