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:

Veröffentlicht unter Fehlerbehebung | Verschlagwortet mit , , , | 1 Kommentar

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
Veröffentlicht unter Tipps und Tricks, Tutorials | Verschlagwortet mit , , , | 2 Kommentare

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.

Veröffentlicht unter Fehlerbehebung | Verschlagwortet mit , , , , , | 1 Kommentar

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.

Veröffentlicht unter Fehlerbehebung | Hinterlasse einen Kommentar

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.

Veröffentlicht unter Fehlerbehebung | Verschlagwortet mit , , , | Hinterlasse einen Kommentar

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.

Veröffentlicht unter Marketing | 1 Kommentar

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.

Veröffentlicht unter Empfehlungen | Verschlagwortet mit , , | Hinterlasse einen Kommentar

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
Veröffentlicht unter Magento Erweiterungen, Tipps und Tricks | Verschlagwortet mit , , , | Hinterlasse einen Kommentar

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;
    }
}
Veröffentlicht unter Fehlerbehebung, Serverkonfiguration, Tipps und Tricks | Hinterlasse einen Kommentar