Blog: Latest Entries (15):


Shopware: XML-OrderExport Plugin

Mein 2. Shopware Plugin (also.. das 2. das in den Community-Store soll..) ist jetzt so gut wie fertig. Es fehlen nur noch ein paar Test und Dokumentation.

bbcode-image


Das Plugin stellt einen Export der Orders bereit. Im Gegensatz zu den eingebauten Export hat man hier ein paar mehr Möglichkeiten das Aufgabeformat (so lange es XML ist) anzupassen und alles zu automatisieren.

Features:

- XML Formate: nativ, openTRANS 1.0 (eher experimentell), openTRANS 2.1
- automatischer Export direkt nach der Bestellung als Datei in ein lokales Verzeichnis
- automatischer Export als XML in einem JSON Container per Push (getestet mit einem Wildfly 11 und einem RestEasy Endpoint)
- Export bestimmter Orders in ein Verzeichnis per CLI
- Abfrage über die REST-API
- REST-API: Als XML in einem JSON-Container (Liste und einzelnd)
- REST-API: Als XML (einzelnd)
- XSLT-TRansformation, damit ist man im Ausgabeformat nicht eingeschränkt (egal ob mit Automatisch, CLI oder REST-API)
- Für die openTRANS-Formate gibt es verschiedene Einstellung für Buyer-Definition und die Party-Ids

bbcode-image


Es ist also ein Plugin was rein auf die Integration von Shopware in vorhandene Bestell-Prozesse mit ERP-Systemen wie SAP ausgelegt ist. Arbeit wirklich gut mit Java Application Servern wie Wirldfly zusammen und auch zum Debuggen ist es sehr praktisch die Bestellungen als XML zu dumpen.

bbcode-image


Ein relativ alter Fork des Plugin wird bei https://www.notebookswieneu.de genutzt, um die Bestellungen als openTRANS 1.0 an das SAP-System
zu übermitteln.

Diese Woche werden die letzten Dinge erledigt und dann wird es hoffentlich Anfang nächster Woche für den Store eingereicht.

Differenz in Tagen mit Java Time-API

Wir berechnen die Differenz in Tagen, für ein Datum, das als String kommt und in der Zukunft liegen muss.


int days = 0;

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
LocalDateTime dateFuture = LocalDateTime.parse(dateValue.trim(), formatter);
LocalDateTime now = LocalDateTime.now();

if(dateFuture.isAfter(now)){
days = Duration.between(now.toLocalDate().atStartOfDay(), dateFuture.toLocalDate().atStartOfDay()).toDays();
}


An sich ganz einfach und hat alles was man sich sonst selbst gebaut hat. Gerade in PHP erinnere ich mich noch sehr daran startOfDay implementiert zu haben.

Shopware: Meine Config für die DEV-VM

Da ich jetzt für die Entwicklung mit Shopware unter Windows eine Ubuntu-VM. Meine Config-Datei für die VM sieht momentan so aus:


<?php return array (
'db' =>
array (
'host' => 'localhost',
'port' => '3306',
'username' => 'root',
'password' => 'root',
'dbname' => 'shopware',
),
'front' => [
'showException' => true,
'throwExceptions' => true,
'noErrorHandler' => false,
],
'phpsettings' => [
'display_errors' => 1,
],
'mail' => [
'type' => 'file',
'path' => $this->DocPath().'mails'
],
);


Damit sollte so weit alles was für die Entwicklung wichtig ist angezeigt werden und auch die Emails sollten gespeichert, aber nicht versendet werden.

Universelles Ant build-script für Shopware-Plugins

Mit dieser build.xml kann man eine Zip-Datei bauen, die von Shopware für Plugins akzeptiert wird. Wenn man Phing und nicht Ant verwendet, muss man ${basedir} durch ${project.basedir} ersetzen.


<project name="build ShopwarePlugin-Zip" default="zip">
<target name="copy">
<basename property="pluginname" file="${basedir}"/>

<mkdir dir="./tmp/building/${pluginname}"/>

<copy todir="./tmp/building/${pluginname}" overwrite="true">
<fileset dir=".">
<exclude name="build.xml" />
<exclude name="${pluginname}.zip" />
<exclude name=".git/**" />
<exclude name=".svn/**" />
<exclude name="tmp/**" />
</fileset>
</copy>

<touch>
<fileset dir="./tmp/building">
<include name="**" />
</fileset>
</touch>
</target>

<target name="zip" depends="copy">
<zip destfile="${pluginname}.zip" basedir="./tmp/building"/>
<delete dir="./tmp"/>
</target>
</project>

Images und Media von einer URL in Shopware

Falls man mal wie in der REST-API ein Bild per URL importieren möchte. Das kann der Fall sein, wenn man sich einen eigenen Importer für ein Format wie BMECat oder so geschrieben hat.


/** @var $media Media */
$media = $this->getMediaResource()->internalCreateMediaByFileLink(
$imageData['imgUrl']
);

$image->setMain(1); //1 is primary image, 2 is the rest
$image->setMedia($media);
$image->setArticle($article);
$image->setPath($media->getName());
$image->setExtension($media->getExtension());
$image->setDescription($media->getDescription());

$image->setPosition($article->getImages()->getCount());


Ist an sich ganz einfach und funktioniert sehr gut.

Die URL kann vom Typ HTTP, HTTPS, FTP, FTPS und FILE sein.

Shopware: Alle Attr-Felder im Export

Ich hatte es schon fertig und dachte ich könnte es später auch mal im Community Store anbieten.. aber dann kam mir schon jemand zu vor und dann auch noch kosten los.

Also hier einfach mein Plugin als Code für jeden:


<?php
namespace HPrExportAttrExtend;

use Doctrine\DBAL\Connection;
use Shopware\Components\Plugin;

class HPrExportAttrExtend extends Plugin{

private $ignoreFields = [
'articleID',
'articledetailsID',
'attr1',
'attr2',
'attr3',
'attr4',
'attr5',
'attr6',
'attr7',
'attr8',
'attr9',
'attr10',
'attr11',
'attr12',
'attr13',
'attr14',
'attr15',
'attr16',
'attr17',
'attr18',
'attr19',
'attr20',
];

public static function getSubscribedEvents(){
return [
'sExport::sCreateSql::after' => 'extendExportArticleSql',
];
}

public function extendExportArticleSql(\Enlight_Hook_HookArgs $args){
$sql = $args->getReturn();

$fields = $this->getAllColumnNames();
if(count($fields) > 0){
$fields[] = 'at.attr20';

$sql = preg_replace("/at\.attr20/i", implode(', ', $fields), $sql);
}

$args->setReturn($sql);
}

private function getAllColumnNames(){
/** @var Connection $dbal */
$dbal = $this->container->get('dbal_connection');
$sql = 'SHOW columns FROM :tablename';
$stmt = $dbal->prepare($sql);
$stmt->execute(['tablename' => 's_articles_attributes']);
$rows = $stmt->fetchAll();
$fields = [];
foreach ($rows as $row){
$name = $row['field'];
if(!in_array($name, $this->ignoreFields)){
$fields[] = 'at.' . $name;
}
}
return $fields;
}
}

Shopware: In einer Ubuntu VM für Windows-Benutzer

Um unseren Shopware-Server aufzusetzen brauchen wir

- Apache (mit php7.0-mod)
- PHP7 (common,mysql,gd,xml,json,xsl,intl,gettext,mbstring,zip)
- MySQL Server
- OpenSSH Server
- PHPMyAdmin (wichtig hier Apache2 auszuwählen.. also mit Sternchen sichtbar nicht nur makiert)

Dann folgt die conf für den Apache und der Eintrag in die Host-Datei.

bbcode-image


Jetzt können wir Shopware downloaden und installieren

bbcode-image


Nun folgt die Netzwerk Konfiguration mit VirtualBox und die Anpassungen der hosts-Datei unter Windows.

bbcode-image


bbcode-image


bbcode-image


bbcode-image


Am Ende können wir mit dem Browser auf Shopware und mit WinSCP auf das System und das Shopware-Verzeichnis zugreifen.

bbcode-image


bbcode-image


So können wir unser lokalen Plugin-Verzeichnis mit dem auf dem Server synchron halten und Änderungen werden automatisch auf den Server gepusht.

Mein 1. Shopware Plugin verfügbar

Es ist soweit.. mein erstes Shopware Plugin ist im Community Store verfügbar.

bbcode-image


Kunden-Registrierung blockieren

Toll fand ich, dass man während des Freigabeprozesses wirklich mit Menschen zu tun hatte und einem auch wirklich von sich aus geholfen wurde.
Meine Erfahrungen beim Mozilla Store für Firefox OS waren da ganz anders. Da erhielt man eine Email mit sehr allgemeinen Punkten gegen was man verstoßen hätte und Hilfe was man noch müssen für die Freigabe gab es so gar nicht.
Deswegen werde ich mich dann jetzt daran machen weitere Plugins zu schreiben und zu veröffentlichen.

Erstmal Shopware mit Vagrant auf meiner Windows Workstation zum Laufen bekommen. Dann geht es sicher alles noch schneller und einfacher.

Oder ich richtige mir doch eine eigene Ubuntu VM ein... mal gucken.

Shopware-Plugin: Customer Register Handling 2.0

Auch wenn die erste Version noch nicht wirklich weiter ist, was die Freigabe betrifft, habe ich hier schon mal die ersten Test mit der 2.0 Version.

bbcode-image


Wenn man einen öffentlich erreichbaren Shop hat, den jeder ohne Umwege von zu hause aus erreichen kann, aber sich nur bestimmte Kunden, wie eigene Mitarbeiter oder Mitarbeiter von Partner Firmen, registrieren dürfen, kann man nun Black- und Whitelists verwenden.

bbcode-image


Darin kann man Email-Adressen oder Domains (z.B. "*@hannespries.de") angeben und invalide Email-Adressen, werden erst gar nicht zum Anmelden zugelassen und der Kunde mit einer Nachricht darüber informiert, dass seine Email-Adresse nicht den Anforderungen entspricht.

Wer also einen Mitarbeitershop hat, kann nun nur Anmeldungen mit der Firmen-Email-Adresse zulassen und muss keine Konten per Hand oder auf Verdacht per Job anlegen lassen.

Shopware: Erstes Plugin auf dem Weg

.. hoffentlich :-) Hat jetzt doch ein paar Monate länger gedauert, bis alles so weit war. Die nächste Version mit White- und Blacklists, die bestimmen, mit welchen Email-Adressen und Domains sich Kunden registrieren dürfen, ist auch schon halb fertig.

Ich hoffe mal, dass das Plugin ohne größere Probleme in einiger Zeit dann in den Store gehen kann und ich dann ein paar andere Ideen für Plugins umsetzen kann.

bbcode-image


Wäre auf jeden Fall toll damit etwas Geld verdienen zu können, da mir Shopware-Plugins sehr viel mehr zusagen als Apps zu Entwickeln oder zu versuchen mit Werbung meine Projekte wenigstens ohne Zuzahlung betreiben zu können.

Vielleicht kann ich dann ja auch https://www.mein-online-adventskalender.de/ als Shopware Einkaufswelt-Element etwas pushen.

Shopware: REST-API und Artikelbilder

Es fällt schnell auf, dass wenn man das Array mit den Bildern im Article-Model der REST-API ersetzt, die neuen Bilder nicht die alten ersetzen, sondern nur hinzugefügt werden. Das funktioniert auch super bei den selben Bildern, so das in einigen Fällen (wenn man nicht weiß, ob es neue Bilder sind oder nicht) sich doppelte Bilder im Artikel häufen.

Zum Glück bietet die REST-API den Merge Mode an. Dieser gilt für Bilder und Kategorien.

Man muss nur ein neues Feld in den Artikel einbauen:


{
"__options_images": {"replace": true},
"images": []
}


Damit wird dann einfach das alte Array geleert bevor die neuen Einträge hinzugefügt werden.

engine/Shopware/Components/Api/Resource/Article.php (5.3.3)


Zeile 2373: $images = $this->checkDataReplacement($article->getImages(), $data, 'images', false);


Default ist hier false. True als Defaultwert hätte ich persönlich logischer gefunden, bzw. eine Einstellung dafür in den Grundeinstellungen, um den Default-Wert zu ändern.

Shopware: Legacy-Plugin Service dekorieren

Gehen wir mal davon aus wir hätten noch ein Legacy-Plugin mit einer Bootstrap-php, die einen Service registriert, den wir dekorieren wollen. Über die services.xml geht so etwas nicht.


class HprTest extends Plugin{
public static function getSubscribedEvents(){
return [
'Enlight_Bootstrap_AfterInitResource_hpr_legacy.the_service' => 'decorateService',
];
}

public function decorateService()
{
$coreService = Shopware()->Container()->get('hpr_legacy.the_service');
$refl = new \ReflectionClass(get_class($coreService));

$field = $refl->getProperty('config');
$field->setAccessible(true);
$config = $field->getValue($coreService);

$service = new TheNewService($config);
Shopware()->Container()->set('hpr_legacy.the_service', $service;
}
}


Sehr sehr unschön.. aber es funktioniert so. Der Weg über die XML ist natürlich sehr viel schöner, gerade weil die Injection der Constructor Arguments vom ursprünglichen Service übernommen werden man nicht diese per Reflections erst einmal wieder aus dem ursprünglichen Service heraus gepult werden müssen.

Shopware: Text-Snippets und Übersetzungen

Am Ende dachte ich mir nur: "ist an sich ja genau so wie in meinem Framework.."

Ok. Alle Sprachen in einer Datei und nicht wie bei den Properties-Dateien in Java, aber ansonsten. Im Grunde hat man eine ini-Datei die eingelesen wird. Sie hat einen Namespace, was es sehr viel einfacher macht auf Text-Snippets anderer Plugins oder des Core-Systems zuzugreifen.


custom/plugins/HPrTest/Resources/snippets/frontend/index/hpr_main.ini


Da kommen dann die Texte rein


[de_DE]
blubb = "Blubb"


Wenn man das Plugin nun installiert oder aktualisiert, kann man die Snippets im Template verwenden.


{s name="blubb" namespace="frontend/index/hpr_main"}{/s}


An sich ja wieder sehr einfach, wenn man erst einmal heraus gefunden hat, wie sich der Namespace bildet und wo man die Datei genau ablegen muss.


Java Time-API.. hart aber gut

Das Arbeiten mit der Time-API von Java ist teilweise echt nicht einfach. Auch gibt es vielmehr zu schreiben und ohne Hilfe aus dem Internet geht es einfach nicht. Wobei ich die ".from()"-Methoden schon wirklich nett finde.


DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
LocalDateTime ldt = LocalDateTime.parse(value.trim(), formatter);

//Calendar
GregorianCalendar gc = GregorianCalendar.from(ldt.atZone(ZoneId.systemDefault()));

//SQL-Timestamp
preparedStatement.setTimestamp(1, Timestamp.from(ldt.atZone(ZoneId.systemDefault()).toInstant()));


Wenn man die letzten 2-3 Jahre fast nur PHP gemacht hat, erschlägt einen es fast schon, von der Fülle an Kombinationen und Klassen die man hier benötigt. Jeden Falls auf den ersten Blick. Auf den zweiten sieht es besser aus und auf den dritten gefällt es einen dann auch schon wirklich.

Und im Gegensatz zum guten alten Date mit SimpleDateFormat funktioniert es auch ohne Probleme.
Java tut auch hier was es am Besten kann: Es zwingt einen es richtig zu machen.. mit ZoneId und allem was man sonst der Bequemlichkeit halber oft lieber weg lässt.

Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?