Bei Deployments die ein per Composer erzeugtes Shopware 6 Projekt als Basis haben (was wohl alle neueren sind) muss man die JWT-Dateien immer noch zusätzlich erzeugen und sie müssen die richtigen Rechte haben.
Man kann auch Env-Variablen (JWT_PUBLIC_KEY und JWT_PRIVATE_KEY) verwenden, was bei mir aber irgendwie nicht korrekt funktionierte und beim Login in die Administration zu einer Exception führt.
Aber es gibt auch einen Weg ganz ohne JWT Keys und der verwendet das APP_SECRET aus der .env Datei.
Damit klappte auch ein Deployment auf platform.sh dann ohne Probleme.
Eine Bestellung zu bearbeiten in dem man z.B. ein LineItem löscht benötigt die Nutzung von Versionen. Was an sich nicht schwer ist, wenn man weiß
wie man es machen muss. (Beispiel ist Shopware 6.4)
An sich geht es ganz einfach. In der Administration geht man auf Update, bestätigt alles, die Plugins werden deaktiviert (vielleicht auch das Language-Pack) und dann startet der Installer und .. läuft in einen Fehler und dann läuft garnichts mehr. Scheint jeden Falls öfters mal so zu passieren.
Ich hab mir ein Script gebastelt mit dem man sich eine Kopie des Shops auf die neue Version updaten kann und dann später auf diese Kopie switchen kann.
Man muss nicht alle Plugins deaktiveren, aber einfacher ist es. Also eine Kopie (Dateien und Datenbank) anlegen und da alles Plugins deaktiveren. Per SQL-Statement geht es recht schnell und einfach.
Der original Shop liegt in shop/ und der neue in shop65/. Die .env der Kopie (wegen DATABASE_URL) wird in shop_shared/ abgelegt und um LOCK_DSN="flock" und SHOPWARE_SKIP_WEBINSTALLER=1 ergänzen.
Dann das Script laufen lassen.. oder besser Zeile für Zeile per Hand ausführen.
cd ~/public_html/shop65 && composer update
cd ~/public_html/shop65 && bin/console system:update:finish
cd ~/public_html/shop65/vendor/shopware/administration/Resources/app/administration && npm install && cd ~/public_html/shop65
Ziel ist es wieder in die Administration zu kommen und dort alle Plugins zu aktualisieren. Wenn das gelungen ist, dann alle nach und nach wieder aktivieren und wieder die Themes in den SalesChannels einrichten.
Wenn Fehler auftreten immer mal wieder bin/console aufrufen, weil dann die Exceptions meistens ganz gut dargestellt wird.
So kommt man auch sehr gut ohne den Installer zu seinem aktuellen Shopware und räumt auch direkt noch etwas auf.
Arbeiten mit Dateien ist in Shopware 6 an sich recht einfach, gerade seit die Media-Entity und deren Thumbnails ein Path-Feld haben, in dem der relative Pfad direkt angegeben werden kann. Wenn man den hat muss man nur noch den absoluten Pfad bauen. Wenn man z.B. in das public/ Verzeichnis will um dort etwas zu hinterlegen oder ein Media-File zu lesen kann man sich die Umgebungsvariable mit Symfony Project Root per Dependency Injection direkt in den Constructor seines Services geben und von da aus dahin navigieren. Von der aktuellen PHP-Datei aus ist es nicht so toll, da man nicht immer weiß wo das Plugin sich befindet. Z.B: kann es im custom-Folder oder irgendwo in vendor/ sich befinden.
<argument>%kernel.project_dir%</argument>
Und was ist wenn man die Dateien in einem Cluster-Betrieb über ein S3-Bucket an die Cluster-Nodes verteilt? Shopware 6.5 hat zum Glück nicht nur Flysystem dabei, sondern nutzt es auch richtig. Man kann sich direkt per Dependency Injection das privater oder das öffentliche Dateisystem geben lassen und dann ist es egal ob es auf einem FTP, in einem S3 Bucket oder im lokalen Dateisystem liegt.
namespace HPr\FSTest\Services;
use League\Flysystem\FilesystemOperator;
use Shopware\Core\Content\Media\MediaEntity;
class MediaTest {
public function __construct(private FilesystemOperator $filesystem){}
/**
* @throws FilesystemException
*/
public function md5Media(MediaEntity $media): string {
return md5($this->filesystem->read($media->getPath()));
}
}
Um nun das passende Dateisystem zu bekommen ist nicht viel nötig.
Public ist das öffentliche Verzeichnis, das man für Product-Bilder, CMS-Media oder auch andere Downloads nutzen kann, die jeder sehen darf. Dann gibt es auch das private Dateisystem, wo man alles wie Rechnungen und Dinge ablegt, die nicht jeder sahen darf und wo man den Zugriff am besten durch einen eigenen Controller kapselt. Die MediaEntity hat einen private-Flag, um anzugeben in welchem Dateisystem man die Datei findet.
Das Dateisystem selbst kann man in einer YAML-Datei in config/packages/ definieren. Wie man da z.B. seine Dateien in einem MinIO S3 Bucket ablegen kann, habe ich in einem Post vorher schon erklärt.
Da man beim einfachen Entwickeln nicht ein AWS S3-Bucket für die Entwickler bereit stellen möchte, kann man hier sehr gut MinIO verwenden. Es lässt sich schnell in docker-compose einbinden und die FileSystems von Shopware können den normalen S3-Adapter verwenden.
Manchmal braucht einfach Imagick. Z. B. wenn man eine Bildvorschau einer PDF erzeugen will oder einfach mehr Power bei der Bildbearbeitung in PHP oder in Scripten braucht.
Während die Installation die meisten Anleitungen für Docker und Imagick mit den default PHP Docker-Image super funktionieren ist es bei Dockware anders, weil es eine volle Ubuntu-Umgebung mitbringt.
Zu beachten ist, dass man für alle PHP-Versionen die Erweiterung installieren muss.
Manchmal funktionieren ein paar Snippets nicht. Ich vermute es liegt daran wie ein Theme über nicht immer sehr gradlinige Wege überschrieben und erweitert wurde.
Z.B. steht dann "orion.footer.certificates" auf der Seite, obwohl die Snippets korrekt für alle Sprachen in der Administration gefunden werden. Also an sich sollte es dann ja funktionieren.
Lösung: Einmal den Übersetzungen ein 'X' anhängen, speichern, das 'X' wieder entfernen und erneut speichern. Dann sind sie in der Storefront auch richtig.
Weil sie dann aus der Datenbank geladen werden und nicht aus dem Theme/Plugin/App.
Zum 2024-01-01 hat One-DC Teile ihrer Dropshipping-Services abgeschaltet. Man kann immer noch Bestellungen dort per API aufgeben und diese direkt an seine Shopkunden senden, aber es gibt keine Feeds mit Produkten, Beständen und Preisen mehr. Der Sinne erschließt sich mir überhaupt nicht, da Dropshipping ja doch irgendwie weiterhin möglich ist und elektronische Katalogdaten auch für PIMs und Kassensysteme der Kunden wichtig ist. Hätte man den Versand an die Endkunden eingestellt würde ich es ja noch verstehen, aber so macht es für mich keinen Sinn. Falls jemand mehr Weiß oder eine Möglichkeit kennt Katalogdaten weiterhin zu erhalten, bitte sich per Email bei mir melden.
Import2Shop stellt deren Anbindung zu EDC auch ein und die Shops sitzen jetzt und versuchen möglichst schnell zu einem anderen Anbieter zu wechseln. Meine Plugins bleiben weiter online, erhalten aber keine Weiterentwicklung mehr und gelten ab sofort als EOL, wenn nicht sich doch noch was neues ergeben sollte.
Wie man eine eigene Entity in die Suche integriert hatte ich schon erklärt. Was aber wenn man eine vorhandene Entity um weitere durchsuchbare Felder erweitern will? Das geht auch relativ einfach.
if (module?.manifest?.defaultSearchConfiguration) {
module.manifest.defaultSearchConfiguration = {
...module.manifest.defaultSearchConfiguration,
extensions: {
// In case some other plugin has already done this trick; we do not want to remove theirs.
...(module.manifest.defaultSearchConfiguration.extensions ?? {}),
// Add our extension fields to the omnisearch
customFields: {
customer_debitor_set_number: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
},
}
},
};
}
Auch wenn dort die Felder hierarchisch angegeben werden, sind diese bei den Snippets flach strukturiert.
In einem Cart-Validator sollte man vermeiden vom Cat-Service die getCart()-Methode zu verwenden. Ich habe einen Service der mir für den Validator benötigte Daten lieferte und dabei auch einen Wert aus dem Cart generierte. getCart() triggert aber wieder den Validator.. Endlossschleife! Also am besten wirklich nur das in die Validator-Methode rein gereichte Cart-Object verwenden.
Es gibt manchmal CustomFields in denen man Daten wie externe Ids, ein Import- oder Export-Datum oder ein einfaches Bool-Flag speichern möchte. Der normale Admin-Benutzer darf diese Daten gerne sehen sollte sie aber nicht ändern, weil er oder sie nicht das nötige Wissen über die internen Abläufe des Plugins hat, um genau zu wissen, welche Auswirkungen so eine Änderung hat.
Deswegen ist es gut so ein CustomField readonly zu machen und am Besten komplett zu disablen. Das ist über die config des CustomFields sehr einfach möglich. In der Manifest einer App kann dass leider schon wieder ganz anders sein, weil dort sowas nicht vorgesehen ist.
Oft ist es sehr viel einfacher direkt etwas in die Suche der Administration einzugeben, als umständlich eine Seite zu öffnen und etwas aus der Liste per Hand oder Browser-Suche heraus zu suchen.
Eigene oder fehlende Entitäten dort zu integrieren ist an sich recht einfach und logisch. Es gibt hier eine Anleitung die aber leider so für mich nicht funktioniert hat, weil ein wichtiger Teil fehlte.
Routennamen sind hier erstmal nur Beispiel haft vergeben.
Step 1 Ich gehe davon aus das ein Plugin existiert mit einem JS-Module, das mindestens eine Route hat und dessen Name nach dem Schema {vendor}-{name} aufgebaut ist. Zum Module müssen wir wie beschrieben
einige wenige Dinge ergänzen:
{
...
entity: 'ce_my_entity',
...
}
hier kommt später noch was dazu!
Step 2
Den Type (der Entität) hinzufügen. Der Name muss nicht dem Namen der Entität entsprechen, ist aber nicht verkehrt es so zu machen.
Damit kennt die Suche nun den neuen Type und dann theoretisch schon danach suchen.
Step 3
Jetzt müssen wir festlegen wie unsere Entität bei den Ergebnissen dargestellt werden soll. Dafür erweitern wir ein Template und machen es der Suche bekannt.
Was noch fehlt Soweit ist alles gut und nach Anleitung. Aber es funktionierte einfach nicht. Es wurde nach allen möglichen Entitäten gesucht nur nicht nach der eigenen. Nach viel Gesuche kam ich dann darauf, dass bei den Preferences, die für die Liste der Entities genutzt wird, meine eigene garnicht aufgelistet wurde. Warum? Weil ich natürlich keine Preferences dafür hinterlegt hatte, weil es nirgendwo angegeben war.
Diese Preferences findet man im Profile seine Admin-Users und kann es dort alles noch genauer anpassen, wie die Suche suchen soll. Hier werden nun die Felder name und description angeboten und auch direkt aktiviert.
Damit funktionierte die Suche dann auch sofort wie gewünscht.
Wenn man sich in einer Shopware 6 SaaS Umgebung und Apps bewegt, hat man nicht mehr die Möglichkeit Rules im PageLoader zu prüfen und ein bool-Value rein zu reichen, weil man nun alles via Twig machen muss. Entweder im Template oder in den App Scripts, die die PageLoader-Events ersetzt haben.
Geht zum Glück an ganz einfach auch wenn es kein array_intersect gibt.
{% set hideBuyButton = false %}
{% set checkRuleIds = config('MyApp.config.checkRules') %}
{% set intersect = checkRuleIds|filter((rule) => rule in context.context.ruleIds) %}
{% if intersect|length > 0 %}
{% set hideBuyButton = true %}
{% endif %}
Nach einigen Hin und noch mehr Her, hat die Interspark Inc bestätigt, dass sie keine IPs der Interspark GmbH übernommen hat und somit auch nicht die IPs an den beiden Plugins besitzt. War am Ende sehr schnell feste gestellt und ich übernehme die Plugins nun wieder, da es ja sonst niemanden gibt, der Ansprüche erhebt. Die Lizenz wird auf die MIT/BSD Lizenz geändert und die GitLab Projekte werden öffentlich.
Während die Shopware 5 Version schon in mehreren Shops bewiesen hat, das sie funktioniert, steht bei der Shopware 6 Version noch aus, diese als production-ready zu deklarieren.
Sie hat die meisten Test gut gemeistert und sollte nun auch mit Shopware 6.5 funktionieren. Trotzdem steht noch aus diese in einer produktiven Umgebung über einige Tage laufen zulassen.
Builds und Releases wird es ab jetzt hier geben. Jeder der Interesse oder Bedarf hat kann sich die Plugins installieren oder auch gerne mit weiter entwickeln.
Eine App-Version des Plugins, um damit auch die Shopware 6 Cloud Version (SaaS) abzudecken ist bis jetzt nicht von mir geplant, außer jemand würde mir dafür 5000 EUR geben, weil der Aufwand wirklich groß ist. Es wäre eine vollständig eigenständige Software, die außerhalb von Shopware läuft und dort wo die DAL verwendet wird dann die Shopware API (mit Multi-Tenant usw) aufrufen würde.
Ich weiß, dass als ich die Namen der Trigger zum ersten mal benutzt hatte, ich keine Übersetzungen brauchte und auch etwas verwundert war, dass es einfach so ging. Hat sich nun wohl geändert und es läuft alles nach dem Schema sw-flow.triggers.XXXX.
Also checkout.order.export.success spaltet sich auf in:
Wenn man den Lieferschein erzeugt, fällt manchmal auf dass das eine Datum sich im Format stark unterscheidet. Es wäre so als würde es eine andere Locale als die anderen benutzen.. oder gar keine.
Der Fehler liegt direkt in der delivery_note.html.twig:
% block document_side_info_contents %}
{{ parent() }}
<tr><td>{% trans with {'%deliveryDate%': config.custom.deliveryDate|format_date('medium', locale=order.language.locale.code)} %}document.deliveryDate{% endtrans %}</td></tr>
{% endblock %}
[code]
Wenn man sich die anderen Datum-Formate in der base oder letter_header Datei anguckt fällt auf, dass hier das Locale nicht aus der Order stammt bzw nicht daraus gelesen wird. Korrekt wäre hier also:
[code]
% block document_side_info_contents %}
{{ parent() }}
<tr><td>{% trans with {'%deliveryDate%': config.custom.deliveryDate|format_date('medium', locale=locale)} %}document.deliveryDate{% endtrans %}</td></tr>
{% endblock %}
Nach dem ich einmal gesehen habe, dass jemand aus dem Meta-Informationen von page sich per über den Meta-Title den Namen der aktuellen Kategorie geholt hat und dieser nur der Name ist solange kein SEO-Title vergeben wurde, hier ein mal der korrekte Weg:
{{ page.header.navigation.active.name }}
Etwas versteckt, aber da findet man die Category-Entity und darin den Namen (natürlich auch mit entsprechender Übersetzung).
Falls man seinen Shopware 6 in einem Shared-Hosting Paket von Cyon (Schweiz) betreibt, ist man es ja schon fast gewohnt, dass es Probleme beim Bauen der Administration über CLI gibt. Momentan gibt es wohl Probleme mit Puppeteer. Die Lösung hier ist es vor eine entsprechende Umgebungsvariable zu setzen:
export PUPPETEER_SKIP_DOWNLOAD='true'
Dann geht es wieder.. nicht schnell aber das war es dort ja noch nie.
Meine erste Lösung war es den MD5-Hash als Dateiname zu nehmen. Dann später kam wollte ich doch lieber ein CustomField dafür nutzen (wie ich auch schon in der Shopware 5 Version ein Attribute genutzt hatte). Funktionierte alles super bis ich dann auf ein System kam, das nicht im dev-Mode lief. Am fileName hängt wohl mehr als man denkt und ich habe gelernt, dass man in einem produktiven Shopware 6 nicht den fileName ändern sollte. Kommt mit auf meine Liste der ganz großen Shopware 6 Nein-Nein's.
die Art und Weise erinnert mich etwas wie ich damals bei meinen cJSv2-Framework die Controller an Elemente gebunden und initialisiert habe.. der default-Name der Init-Methode war da auch init.
Der DomAccess-Helper ist super und sollte man so verwenden. Über den kann man auch den QuerySelector nutzen und dass schützt hoffentlich davor, dass ein Haufen klassischer Webdesigner versuchen JQuery wieder da rein zu bringen. Der HttpClient ist auch nett aber man kann genau so gut direkt mit fetch() arbeiten, wenn man nicht unbedingt irgendwelche Steinzeit Browser supporten möchte. Aber besonder mag ich von den Helpern DeviceDetection.isTouchDevice() weil man damit einfach unterschieden kann ob man gerade ein Touch oder ein MouseEnter
Event nutzen muss.
Wenn man in der Storefront an einem eignem JavaScript-Plugin arbeitet ist es beim Debuggen, oft nervig, dass der JavaScript Code nicht direkt lesbar ist. Oft weiß man beim eigenen Code direkt was los ist, aber gerade wenn 3rd Party Plugins auch mit rein spielen, kommt man nicht drum herum sich Exceptions mit lesbaren Stacktrace anzeigen zu lassen.
Das ist zum Glück extrem einfach:
* Port 9998 aus dem Docker-Container durchleiten
* Dem SalesChannel in dem man entwickelt die Domain "localhost" zu ordnen
* das Script bin/watch-storefront.sh starten
Nun kann man localhost:9998 im Browser aufrufen. Der JavaScript Code ist lesbar und Änderungen werden sofort übernommen ohne dass man die Storefront neu bauen muss.
Einmal schnell gucken, wie viele und welche Art von Produkten über welchen SalesChannel und URL zu kaufen sind.
SELECT x.url, 'single' type, count(x.id)
FROM (
select d.url, p.id, p.parent_id, count(p2.id) children from sales_channel_domain d
join sales_channel sc on d.sales_channel_id = sc.id
join product_visibility pv on pv.sales_channel_id = sc.id
join product p on p.id = pv.product_id
left outer join product p2 on p.id = p2.parent_id
where sc.active = 1
group by d.url, p.id, p.parent_id
)x
WHERE x.parent_id is null and x.children < 1
group by x.url
union all
SELECT x.url, 'main' type, count(x.id)
FROM (
select d.url, p.id, p.parent_id, count(p2.id) children from sales_channel_domain d
join sales_channel sc on d.sales_channel_id = sc.id
join product_visibility pv on pv.sales_channel_id = sc.id
join product p on p.id = pv.product_id
left outer join product p2 on p.id = p2.parent_id
where sc.active = 1
group by d.url, p.id, p.parent_id
)x
WHERE x.parent_id is null and x.children > 0
group by x.url
union all
SELECT x.url, 'variant' type, count(x.id)
FROM (
select d.url, p2.id, p2.parent_id, 0 children from sales_channel_domain d
join sales_channel sc on d.sales_channel_id = sc.id
join product_visibility pv on pv.sales_channel_id = sc.id
join product p on p.id = pv.product_id
left outer join product p2 on p.id = p2.parent_id
where sc.active = 1
)x
group by x.url
Gerade in Migrations kommt man manchmal in darum herum mit den UUIDs arbeiten zu müssen. Da es binary-Daten sind muss man etwas mit den anstellen, um die aus Shopware 6 bekannte Darstellung zu erreichen und auch um diese wieder die in Datenbank zu bekommen.
Lesen der Id: Um die aus Shopware 6 gewohnte Darstellung zu bekommen muss man in MySQL die HEX-Function verwenden. Zusätzlich muss der String noch in Kleinbuchstaben umgewandelt werden.
Schreiben der Id: Zuerst wieder alles in Großbuchstaben umwandelnt. Dann muss mit der MySQL-Function UNHEX der Hex-String wieder in binary Data umgewandelt werden.
Es bleibt zwar etwas umständlich, aber ist damit durch aus handhabbar. Da es in dem Sinne kein Autoincrement gibt für die UUIDs hilft dann dort Uuid::randomHex().
Bei Shopware 6 ist es etwas anders das Platzhalter-Bild für Produkte ohne Bilder zu ersetzen. An sich ist es ganz einfach, wenn man weiß wie es geht. Auch Anleitungen aus dem Internet von 2019 sind nicht mehr aktuell, denn es geht nun noch einfacher!
DAs Bild ist ein SVG-Icon mit dem Namen 'placeholder'. Man kann mit der Suche schnell herausfinden, wo es überall verwendet wird. Nun muss man nur ein eigenes Icon in per Plugin einbringen. Das ist auch an sich sehr einfach. Man legt einen Pfad in seinem Plugin an "src/Resources/app/storefront/dist/assets/icon/myiconpack/" und kopiert dort seine SVG-Datei rein. Die SVG kann auch gerne als placeholder.svg bennant sein. Nun muss man den Twig-Tag "sw_icon" noch um zwei Werte ergänzen. "pack" ist der oben im Pfad genutzte Icon-Pack Name und der "namesapce" ist einfach der Name des Plugins in dem man sich gerade befindet.
Einfach mal die Meta-Description mit einem Prefix versehen, um kleinere SEO-Probleme zu beheben. Klingt einfach und führt schnell zur Verzweiflung. frontend/detail/header.tpl angepasst und es funktioniert nicht. Keywords ändern ist kein Problem nur die Meta-Description des Artikels will sich nicht ändern.
Lösung Wie man schnell vermutet wird die eigene Änderung überschrieben. Der Schuldige ist das SEO-Plugin und da muss man die index.tpl anpassen. Die Änderung kann z.B. so aussehen:
Während ja jede Entity eine id hat, hat nicht jede ein name-Field. Ich musste doch etwas suchen bis ich erstmal die Dokumentation zu der Component gefunden habe und dann mir klar war wie ich ein andere Feld angeben kann. Am Ende natürlich genau so wie man es sich dachte nur der Prop-Name ist irgendwie seltsam, weil Label ja auch anders dort verwendet wird als Begriff.
Ich musste mir eine sw2-Datenbank anlegen über root/root, aber an sich sollte es wie in meinem SW5-Environment auf mit der sw-Datenbank und sw/sw gehen.
Ich hatte für meinen Blog leider die letzten Monate wenig Zeit. Viel zu tun bei der Arbeit, daneben und auch noch ein Homeoffice einrichten inkl. Renovierung. Deswegen hier in einer sehr kurzen Form was ich so mal sagen wollte:
* API-Platform: mähhh... ein Springboot finde ich besser und flexibler
* Next.js: React für PHP-Developer... so mag ich React
* Shopware und Translations: s_article_tranlsations ist nur Caching und s_core_translations ist alt und nicht hübsch
* Bootstrap 5: endlich da und sieht aus wie Bootstrap 4 in noch besser (kein JQuery mehr!)
* Sunlu SPLA: totaler Schrott gefühlt... nicht stabil genug
* Webcams: 300%ige Preissteigerungen.. ich war/bin beeindruckt
* Confluence: Ich habe lange mit geweigert und überlegt, aber nun nutze ich auch Confluence für mich selbst
* Cyberpunk 2077: Gutes Spiel, aber gerade aus Entwicklersicht sehr interessant. wie der Release lief und wie die QK/QS doch grandios in einigen Fällen scheitern kann
* Angebot vor paar Wochen - 150 EUR 4K Samsung Monitor: Super für den Preis, zerkratzt leider leicht, ideal für das Homeoffice
* DECT Thermostate: lohnt sich sowas und würde es im Homeoffice(-Dachgeschoss) wirklich Kosten sparen? China Wifi Lösungen sind zwar günstiger aber die AVM-Lösung sieht für mich besser aus, weil es keine Cloud braucht. Integration mit Alexa?
Manchmal möchte man Artikel für 0.00 Euro anbieten. Zum Beispiel irgendwelche Muster-Artikel.
Wenn man nun einfach einen Preis von 0.00 Euro eingibt, gibt es einige seltsame Nebeneffekte. Der Warenkorb funktioniert auch nicht mehr und man findet im Log den Fehler (sBasket::getPriceForAddProduct):
Man kann einen Update-Endpoint für Bestellungen definieren, der angesprochen, wenn sich der Status einer Bestellung bei EDC-Wholesale ändert. Problem ist, dass das nicht immer funktioniert und Hilfe auch nicht immer kommt. Aber man hat Mail die man parsen kann. Die wichtigsten Regex dafür sind:
Betreff:
/Auslieferung Ihrer Bestellung/
Body-Trackingcode:
/>(\d+)<\/b>/
Body-Ordernumber:
/Kontrollnummer:<\/b>\s*(\d+)<br/
Damit kann man alle Daten bekommen und seine Bestellungen updaten. Mit einer Shopware 5 Lösung kann ich gerne auf Anfrage helfen.
Gerade bei den Shopware Plugins war es wichtig, dass sich die Funktionsweise eines Plugins nicht einfach ändert. Ich hab erlebt was passieren kann, wenn sich bei einen Newsletter-Plugin in einer Minor-Version plötzlich das Verhalten änderte. Es wurden plötzlich alle Kunden mit Gruppe übertragen und nicht mehr nur die einer bestimmten Gruppe und weil niemand sich die Änderungen groß angesehen hat, bekamen plötzlich alle den Newsletter. Das war doof.
Kaum jemand liest den Changelog oder nur selektiv nach bestimmten Fixes. Wenn ich als Shopware-Betreiber einen Fix für ein Plugin brauche, habe ich meistens keine Zeit jedes Verhalten und jede Funktion des Plugins nochmal durch zu testen. Ich will nur mein Problem beheben und keine neuen bekommen. Jedes neue Feature muss also in den default Einstellungen erst einmal deaktiviert sein. Nur Fixes dürfen ungefragt greifen.
Funktionen im Beta-Stadium dürfen auch nicht aktivierbar sein, wenn sie nicht stable sind. Unfertiger-Code darf mit ausgeliefert werden, wenn es nicht anders geht! ABER er muss auch hart im Code deaktiviert sein. Oder auch im Build-Prozess aus der Config-XML auskommentiert werden. Da muss dann aber sichergestellt sein, dass geprüft wird, ob das Config-Value überhaupt gesetzt ist und wenn nicht, dass es als deaktiviert gilt.
Aber wenn es nicht zwingend nötig ist, sollte man unfertige Features nie mit ausliefern und per Feature-Flag deaktivieren. Am Ende wird es sicher noch mal überarbeitet und es funktioniert dann anders. Weglassen minimiert das Risiko auf Fehler und dass irgendwer es doch aktiviert und nutzt.
Fazit: Feature-Flags sind wichtig, aber nicht dafür da um unfertige Features mit ausliefern zu können (ein Feature pro Feature-Branch!!!!)
Aus Shopware kennt man das Prinzip, dass man beim Erweitern von Templates einfach "parent:" angeben muss und es wird immer das vorher gehende Template mit dem selben Pfad erweitert. So kann man ein Template mehrmals durch eine unbekannte Anzahl von Plugins erweitern lassen. Twig will aber immer einen Namespace haben. Also muss man heraus finden, mit welchen Plugin man anfängt und welches Plugin dann auf das aktuelle folgt oder man auf das Basis-Template gehen muss. Ich hab mich von der Shopware 6 Implementierung inspirieren lassen und ein kleines Beispiel gebaut, bei dem man die ein Template erweitern kann und die Plugin-Namespaces immer dynamisch ergänzt werden.
Die Verzeichnisstruktur des Beispiels ist sehr einfach:
Die Basislogik habe ich in einer einfachen Function zusammen gefasst. Hier wird entweder das Plugin heraus gesucht mit dem angefangen werden muss oder das Plugin, das auf das aktuelle folgt und auch dieses Template mitbringt.
function findNewBase($template, $list = [], $currentBase = null) {
$result = 'base';
$found = $currentBase == null; //if null, took the first one
foreach($list as $key => $path) {
if($key == $currentBase) {
$found = true;
}
else if ($found && file_exists($path . '/' . $template)) {
$result = $key;
break;
}
}
return $result;
}
Die Integration wird über ein Token-Parser implementiert.
final class ExtTokenParser extends AbstractTokenParser {
/**
* @var Parser
*/
protected $parser;
private $list = [];
public function __construct(array $list)
{
$this->list = $list;
}
public function getTag(): string
{
return 'base_extends';
}
/**
* @return Node
*/
public function parse(Token $token)
{
$stream = $this->parser->getStream();
$source = $stream->getSourceContext()->getName();
$template = $stream->next()->getValue();
$stream->injectTokens([
new Token(Token::BLOCK_START_TYPE, '', 2),
new Token(Token::NAME_TYPE, 'extends', 2),
new Token(Token::STRING_TYPE, $parent, 2),
new Token(Token::BLOCK_END_TYPE, '', 2),
]);
Jetzt fehlt nur noch eine passende include-Funktion und man kann sich selbst ein System bauen, desen Templates sich über Plugins ohne Probleme erweitern lassen. Ich arbeite daran....
Edit: Die vollständige Implementierung mit extends und include ist jetzt auf GitHub zu finden.
Plugins für Shopware 6 zu schreiben ist an sich garnicht so unterschiedlich zu dem selben Vorgang für Shopware 5. Was nur anders geworden ist, dass Shopware eigene XMLs und Enlight-Components durch den Composer und Symfony abgelöst wurden. Man kann nun wirklich Namespaces definieren, die unabhängig vom Plugin-Namen sind und es ist kein Problem mehr den Composer mit Plugins zu verwenden, sondern es ist jetzt der zu gehende Weg.
Setup
Wie gehabt kann man sein Plugin unter custom/plugins anlegen.
custom/plugins/HPrOrderExample
Zu erst sollte man mit der composer.json anfangen.
Wichtig in der composer.json ist einmal der PSR-4 Namespace den man auf das src/ Verzeichnis setzt und der "extra" Teil. Dort wird alles definiert, was mit Shopware zu tun hat. Hier wird auch die zentrale Plugin-Class angegeben, die nicht mehr wie in Shopware 5 den selben Namen des Verzeichnisses des Plugins haben muss. Auch sieht man hier dass der Namespace-Name nichts mehr mit dem Verzeichniss-Namen zu tun hat.
Wir könnten durch die composer.json das Plugin auch komplett außerhalb der Shopware-Installation entwickeln, weil die gesamte Plattform als Dependency eingebunden ist. Deswegen müssen wir auch einmal den Composer ausführen.
php composer install
Plugin programmieren Nun fangen wir mit dem Schreiben des Plugins an.
Die Plugin-Klasse enthält erst einmal keine weitere Logik:
<?php
namespace HPr\OrderExample;
use Shopware\Core\Framework\Plugin;
class OrderExample extends Plugin{
}
Wir wollen auf ein Event reagieren. Dafür benutzen wir eine Subscriber-Klasse. Während man diese in Shopware 5 nicht unbedingt brauchte und alles in der Plugin-Klasse erldedigen konnte, muss man nun eine extra Klasse nutzen und diese auch in der services.xml eintragen. Die Id des Services ist der fullqualified-classname des Subscribers
Resources ist vordefiniert, kann aber in der Plugin-Klasse angepasst werden, so dass diese Dateien auch in einem anderen Package liegen könnten.
src/Subscribers/OnOrderSaved
<?php
namespace HPr\OrderExample\Subscribers;
use Shopware\Core\Checkout\Order\OrderEvents;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class OnOrderSaved implements EventSubscriberInterface {
/**
* @return array The event names to listen to
*/
public static function getSubscribedEvents()
{
return [
OrderEvents::ORDER_WRITTEN_EVENT => ['dumpOrderData']
];
}
public function dumpOrderData(EntityWrittenEvent $event) {
file_put_contents('/var/test/orderdump.json', json_encode($event->getContext()));
}
}
Hier sieht man, dass nur noch Symfony benutzt wird und keine Shopware eigenen Events.
Wir schreiben einfach die Order-Entity, die gespeichert wurde in eine Datei. Dort können wir UUID und ähnliches der Order finden.
Das ganze kann man dann weiter ausbauen und auf andere Order-Events (die man alle in der OrderEvents.php finden kann) reagieren.
Installieren
Damit ist das Plugin auch schon soweit fertig. Den Composer haben wir schon ausgeführt (ansonsten müssen wir es spätestens jetzt machen).
Nun geht es über die UI oder die Console weiter. Dort wird das Plugin installiert und und aktiviert.
Wenn man nun eine Bestellung ausführt wird, deren Entity in die oben angegebene Datei geschrieben.
Der einfachste Weg einen Shopware Shop zuinstallieren war immer, die ZIP-Datei mit dem Webinstaller downzuloaden und diese in das gewünschte Verzeichnis zu entpacken. Dann die URL aufrufen und dem Installer folgen. Das ist für automatische Deployments nicht so toll und oft wurde dort einfach die Git-Version verwendet. Die hat den extremen Nachteil, dass diese unglaublich viele Dinge mitbringt, die in einer produktiven Umgebung nichts zu suchen haben. Buildscripte, Tests, etc bringen einen wirklich nur in der Dev-Umgebung was und sollten in der produktiven Umgebung nicht mit rumliegen, weil je mehr rumliegt, desto mehr Sicherheitslücken oder ungewollte Probleme könnten mitkommen.
Seit einiger Zeit kann man Shopware aber auch sehr einfach über den Composer installieren. Dabei wird eine eher moderne Verzeichnisstruktur angelegt und auch die Basis-Konfiguration kann einfach über Env-Variablen gesetzt werden, so dass ein automatisches Deployment für einen Server damit sehr einfach wird. Im Idealfall hat man die Datenbank schon sauber und fertig vorliegen. Dann erspart man sich fast den gesamten Installationsprozess und kann direkt loslegen.
Wenn man den Composer noch nicht installiert hat, muss man diesen kurz installieren:
<Directory /var/www/your_webshop>
AllowOverride All
Require all granted
</Directory>
RewriteEngine On
</Virtualhost>
Reload des Apache und schon kann es an sich losgehen. Wenn man sehen möchte wie die DATABASE_URL verarbeitet wird, kann man einen Blick in die etwas komplexer gewordene config.php werfen die man nun unter your_webshop/app/config/config.php findet.
Sollte man noch keine fertige Datenbank auf dem Server liegen haben, muss man die ./app/bin/install.sh ausführen. Gerade für mehrere automatische Deployments, würde ich aber die Datenbank einmal local auf meiner Workstation anlegen und mit Default-Werten befüllen. Diese kommt dann auf den Datebankserver und wird beim deployment, mit den spezifischen Daten wie den Shopdaten und Admin-Zugängen versehen.
Natürlich würden Updates auch über den Composer laufen, wobei sw:migration:migrate automatisch mit aufgerufen wird, um die Datenbank mit aktuell zu halten. Das Verhalten kann man über die Deaktivierung des entsprechenden Hooks in der composer.json verhindern (aber das macht an sich nur in Cluster-Umgebungen Sinn). Ein Update über das Webinstaller-Plugin würde Probleme bereiten und sollte, wenn man es dann ,z.B. weil man eine alte Installation umgezogen hat, installiert und aktiv hat mit ./bin/console sw:plugin:uninstall SwagUpdate entfernen.
Der wirkliche Vorteil liegt jetzt darin, dass man in die composer.json seine Git-Repositories von den Plugins mit eintragen kann und die Plugins direkt über den Composer installieren und updaten kann. Man muss also nicht diese erst vom Server downloaden + entpacken oder per Git clonen (wo dann wieder viel Overhead mit rüber kommen würde).
Ein Update auf 5.6 ist schnell und einfach gemacht. Alle Tests liefen super und es sah nach einer 2 Minuten Aktion aus... dann kam das SwagPaymentPaypal und nutzte Shopware::VERSION. Um genau zu sein in der Bootstrap.php in Zeile 146. Das gibt es in 5.6 aber nicht mehr und deswegen lief dann auch gar nichts mehr. Das Plugin lies sich auch nicht deaktivieren.
Wer also das Plugin noch nutzt sollte es unbedingt durch SwagPaymentPayPalUnified ersetzen. Das alte muss deinstalliert werden, weil die selben Config-Namen verwendet werden.
Wer das alte noch hat und in das selbe Problem läuft, muss dann leider die Zeile 146 per Hand ändern. In etwas wie das hier:
if (false) {
Darin wird etwas für Shopware < 4.2.0 gemacht, was wir mit 5.6 eindeutig nicht mehr brauchen.
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?