Blog: Latest Entries (15):


Shopware 6 Filesysteme nutzen

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.


<service id="HPr\FSTest\Services\MediaTest">
<argument type="service" id="shopware.filesystem.public"/>
</service>


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.

Shopware 6 und MinIO

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.

docker-compose.yml:

version: '3'

services:
shopware:
....


minio:
image: minio/minio:latest
volumes:
- minio_data1:/data
ports:
- "9000:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minioKey123
command: server /data
networks:
- shopware-web

createbuckets:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
/usr/bin/mc config host add myminio http://minio:9000 minio minioKey123;
/usr/bin/mc rm -r --force myminio/public;
/usr/bin/mc mb myminio/public;
/usr/bin/mc anonymous set public myminio/public;
exit 0;
"
networks:
- shopware-web

volumes:
minio_data1:
driver: local

networks:
shopware-web:
external: false


Nun kann man für einzelne der getrennten Dateisysteme das Bucket als Storage hinterlegen (config/packages/shopware.yaml):

shopware:
filesystem:
public:
type: "amazon-s3"
url: 'http://s3.local:9000/public'
config:
bucket: "public"
endpoint: "http://minio:9000"
use_path_style_endpoint: true
region: 'local'
credentials:
key: minio
secret: minioKey123
options:
visibility: "public"
theme:
type: "amazon-s3"
url: 'http://s3.local:9000/public'
config:
bucket: "public"
endpoint: "http://minio:9000"
use_path_style_endpoint: true
region: 'local'
credentials:
key: minio
secret: minioKey123
options:
visibility: "public"
cdn:
strategy: md5


Man kann auch getrennte Buckets nutzen, aber das macht an sich wenig Sinn.

Nun Cache leeren, Themes compilieren und die Medien einmal neu importieren oder auf in das Bucket kopieren.

Das kann man gut mit dem lokalen Client machen:

wget http://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin
mc config host add myminio http://localhost:9000 minio minioKey123


Die Dokumentation ist gewöhnungsbedürftig und ChatGPT liefert auch gerne veraltete Anleitungen.. aber mit etwas suchen klappt es dann doch recht gut.

Shopware 6: In Dockware Imagick installieren

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.


FROM dockware/dev:6.5.8.6

RUN sudo su
RUN sudo apt update \
&& sudo apt install --no-install-recommends --assume-yes --quiet \
imagemagick \
php-imagick \
php8.1-imagick \
php8.2-imagick \
php8.3-imagick \
&& sudo rm -rf /var/lib/apt/lists/*

Shopware 6 Tipp: Nicht funktionierende Snippets

Ein Tipp der eher für Benutzer ist.

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.

Shopware 6: Associations sortieren

Eine kleines Beispiel wie man Associations sortiert, weil es wohl nicht so einfach ist, so eines zu finden.


$criteria->addAssociation('lineItems');
$criteria->getAssociation('lineItems')->addSorting(new FieldSorting('position'));


In einer einzigen Zeile geht es nicht, weil dann die falsche Criteria genutzt wird.

Filter und ähnliches könne so auch gesetzt werden.

EDC/One-DC ist EOL

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.

PHP: XLSX einfach lesen

Bei in Java bei XML ist die bekannteste Lösung manchmal nicht die Lösung, die man gerade braucht. Groß, komplex und kann alles. Dependencies machen dann aber Probleme und wenn man nur eine Datei schnell und einfach lesen möchte, braucht man nicht irgend eine HTML-Lib, die nur in ganz bestimmten Fällen nötig wäre.

Beim Lesen von Excel-Dateien in PHP ist es genau so. HTML-Lib machte Probleme beim Installieren über Composer, aber ich will ga rkeine HTML-Sachen damit machen. Cool das es gehen würde, aber ich will nur schnell und einfach die Daten der Tabelle auslesen. CSV hätte ja gereicht, aber es kommt eben eine Excel-Datei.

Dann fand ich SimpleXLSX.

Super einfach zu benutzen. Hat keine weiteren Dependencies und liefert direkt die Tabelle als ein plain old Array.

bbcode-image


Code (XLSX to JSON):

<?php
include_once __DIR__ . '/vendor/autoload.php';

use Shuchkin\SimpleXLSX;

if ( $xlsx = SimpleXLSX::parse(__DIR__ . '/pets.xlsx') ) {
$json = [];
$head = $xlsx->rows()[0];

foreach ($xlsx->rows() as $idx => $row) {
if($idx > 0) {
$item = [];
foreach ($head as $idxKey => $key) {
if(strlen($key) > 0) {
$item[$key] = $row[$idxKey];
}
}
$json[] = $item;
}
}

echo json_encode($json);
} else {
echo SimpleXLSX::parseError();
}


Ergebnis:

[
{
"Name": "Miki",
"Rasse": "Hund",
"Farbe": "braun"
},
{
"Name": "Teddy",
"Rasse": "Hund",
"Farbe": "wei\\u00df"
},
{
"Name": "Merle",
"Rasse": "Katze",
"Farbe": "wei\\u00df|braun|schwarz"
},
{
"Name": "Finja",
"Rasse": "Katze",
"Farbe": "braun|schwarz"
},
{
"Name": "Ronja",
"Rasse": "Katze",
"Farbe": "wei\\u00df|schwarz"
},
{
"Name": "Molly",
"Rasse": "Katze",
"Farbe": "grau"
}
]

Development: FTP-Sync anstelle von Docker

Viele erinnern sich noch an Zeiten, wo man direkt auf einem Webserver seinen HTML-Seiten und Scripts geschrieben und getestet hat. HTML ging meistens schon lokal, aber wenn es um PHP oder anderes ging brauchte man einen Server. Dann schwenkte man auf XAMPP um, wo man einen lokalen Apache nutze. Linux brachte den Apache und PHP direkt mit. Aber man hatte oft kein Linux und half sich mit VirtualPC oder VirtualBox, so man entweder eine shared Speicher hatte oder ganz klassisch per FTP oder später SCP/SSH seine Dateien aus der IDE ins Zielsystem bekam. Dann kam Docker und die Welt wurde gut.. über all gut? Nein, dann erstaunlich viele gerade im Agentur-Bereich arbeiten immer noch mit einem Server und einem FTP-Sync. Gut heute oft mit SFTP oder SCP, aber ohne Docker oder lokalen Webserver.
Während ich klassische vServer mit Apache und ohne Reverse-Proxy und Docker für veraltet halte, sind sie noch öfter Realität als Docker-/K8n-Umgebungen. Selbst shared-Hosting für produktive Umgebungen sind noch öfters anzutreffen.

Nach einem Gespräch, wo noch direkt auf dem Server gearbeitet wurde und nicht mal eine lokale IDE einen Sync in Richtung Server vornahm sondern direkt die Datei vom Server aus geöffnet wurde (da kann man fast direkt mit vi auf dem Server arbeiten...), hier eine einfache kostenlose Lösung, wo man wenigstens die Dateien lokal hat und so auch ohne Probleme mit Git arbeiten kann.

Genutzt wird VisualStudio Code (die Intellij-IDEs bringen so einen Sync direkt von Haus aus mit, kosten aber in den meisten Varianten Geld).

Ein Plugin installieren:
bbcode-image


FTP-Config anlegen (wird geöffnet nach dem ersten Sync-Versuch):
bbcode-image


Wenn uploadOnSave aktiviert ist am Besten die IDE noch mal neustarten.

Geht auf jeden Fall besser als WinSCP parallel zum Sync laufen zu lassen.

Würde ich so entwickeln wollen? Nein. Besonders wenn mehr als ein Entwickler an einem Projekt arbeiten, geht nichts über Docker. Für Shopware habe ich gute Images oder man nimmt Dockware, was gerade für Entwickler an sich vollkommen reicht.

Shopware 6: Administration-Suche erweitern

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.

Basiert auf diesem Forums-Post: https://forum.shopware.com/t/admin-such-optimierung-default-search-configuration-js/94397/3


const module = Module.getModuleByEntityName('customer');

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.


{
"sw-profile": {
"tabSearchPreferences": {
"modules": {
"customer": {
"customer_debitor_set_number": "Debitor-Nummer"
}
}
}
}
}


Jetzt kann man die Darstellung der Suchergebnis-Items verbessern, damit man weiß, was man wo gefunden hat. Aber das ist an sich erstmal optional.

bbcode-image

Shopware 6 Tipp: Cart-Validator und Cart-Service

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.

Shopware 6: Readonly und Disabled CustomFields

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.

bbcode-image

Shopware 6: Eigene Entitäten in der globalen Admin-Suche

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.

https://developer.shopware.com/docs/guides/plugins/plugins/administration/search-custom-data.html

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.


Application.addServiceProviderDecorator('searchTypeService', searchTypeService => {
searchTypeService.upsertType('ce_my_entity', {
entityName: 'ce_my_entity',
placeholderSnippet: 'search.general.placeholderSearchBar',
listingRoute: 'hpr.searchexample.index',
});
return searchTypeService;
});


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.

Twig:

{% block sw_search_bar_item_cms_page %}
{% parent %}

<router-link v-else-if="type === 'ce_my_entity'"
v-bind:to="{ name: 'hpr.searchexample.detail', params: { id: item.id } }"
ref="routerLink"
class="sw-search-bar-item__link">
{% block sw_search_bar_item_ceme_label %}
<span class="sw-search-bar-item__label">
<sw-highlight-text v-bind:searchTerm="searchTerm"
v-bind:text="item.name">
</sw-highlight-text>
</span>
{% endblock %}
</router-link>
{% endblock %}


JavaScript:

Shopware.Component.override('sw-search-bar-item', {
template: templateItem
});


Step 4

Jetzt muss die Suche nur noch wissen wie unsere Entität bezeichnet werden soll, was wir bei den Snippets des Modules mit hinterlegen.


{
"global": {
"entities": {
"ce_my_entity": "Meine Entität"
}
}
}


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.

Das JS-Module muss also so aussehen:

{
...
entity: 'ce_my_entity',
defaultSearchConfiguration: {
_searchable: true,
name: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
},
description: {
_searchable: true,
_score: searchRankingPoint.HIGH_SEARCH_RANKING,
}
}
...

}


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.

Shopware 6: Rules in Twig prüfen

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 %}

Shopware 6: HTML-Sanitizer deaktivieren

Während man in 6.4 noch beliebigen eigenen HTML-Code in z.B. CMS-Elementen oder Snippets eingeben konnte, filtert 6.5 Teile dieses Codes nun heraus. Er gilt als möglicherweise unsicher. Wenn man nun von 6.4 auf 6.5 migriert und z.B. style-Tags entfernt werden, wäre es sehr aufwendig alles nun in SCSS und dem Theme unterzubringen. Einfacher ist es den Sanitizer zu deaktivieren und das selbe Verhalten wie bei 6.4 wieder zu haben.

bbcode-image


In der config/packages/shopware.yaml kann den Sanitizer einfach deaktiveren.


shopware:
html_sanitizer:
enabled: false

Shopware-Dev Docker-Setup

Update meiner Shopware Docker Umgebung. Funktioniert mit 6.4. An 6.5 arbeite ich noch. Es ist Imagick installiert, um z.B. automatisch beim Upload von PDFs die erste Seite als JPG zu speichern und in einem CustomField als Vorschau zu verlinken.

docker-compose.yml

version: '3.3'

services:
sw_mysql:
image: bitnami/mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: sw
MYSQL_USER: sw
MYSQL_PASSWORD: sw
volumes:
- ./db_vol/:/bitnami/mysql/data

sw_http:
build: ./containerData/apache
volumes:
- ./containerData/apache/my_vhost.conf:/etc/apache2/sites-enabled/000-default.conf:rw
- ./sw:/var/www/html:rw,delegated
ports:
- 80:80
- 443:443
- 8080:8080
- 8090:8090

mail:
image: mailhog/mailhog
ports:
- 8025:8025

adminer:
image: adminer
ports:
- 8086:8080
depends_on:
- sw_mysql


containerData/apache/Dockerfile

FROM php:8.2-apache

RUN apt-get update &&\
apt-get install --no-install-recommends --assume-yes --quiet ca-certificates \
curl \
git \
libxml2-dev \
libxslt-dev \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
libcurl4-gnutls-dev \
zlib1g-dev \
libzip-dev \
nano \
unzip \
ghostscript \
libmagickwand-dev \
&& docker-php-ext-install -j$(nproc) iconv \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd

RUN apt-get install -y libc-client-dev libkrb5-dev \
&& docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
&& docker-php-ext-install imap

RUN docker-php-ext-install dom \
&& docker-php-ext-install pdo \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install curl \
&& docker-php-ext-install zip \
&& docker-php-ext-install intl \
&& docker-php-ext-install xml \
&& docker-php-ext-install xsl \
&& docker-php-ext-install fileinfo

RUN mkdir -p /usr/src/php/ext/imagick
RUN curl -fsSL https://github.com/Imagick/imagick/archive/06116aa24b76edaf6b1693198f79e6c295eda8a9.tar.gz | tar xvz -C "/usr/src/php/ext/imagick" --strip 1
RUN docker-php-ext-install imagick


RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh \
&& bash nodesource_setup.sh \
&& apt-get install -y nodejs

RUN rm -rf /var/lib/apt/lists/*

#RUN pecl install xdebug-2.8.0 && docker-php-ext-enable xdebug
#RUN echo 'zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so"' >> /usr/local/etc/php/php.ini
#RUN echo 'xdebug.remote_port=9000' >> /usr/local/etc/php/php.ini
#RUN echo 'xdebug.remote_enable=1' >> /usr/local/etc/php/php.ini
#RUN echo 'xdebug.remote_host=host.docker.internal' >> /usr/local/etc/php/php.ini

RUN echo 'memory_limit = 512M' >> /usr/local/etc/php/php.ini

RUN a2enmod rewrite

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php --2.2 #there are problem
RUN mv composer.phar /usr/local/bin/composer

# copy conf-file to /etc/apache2/sites-enabled/000-default.conf
RUN mkdir /files;
COPY ./setup.sh /files/setup.sh
ENTRYPOINT ["sh", "/files/setup.sh"]


containerData/apache/my_vhost.conf

<VirtualHost *:80>
ServerName localhost
ServerAlias www.localhost

DocumentRoot "/var/www/html/public"

RewriteEngine On
RewriteMap lc int:tolower

<Directory "/var/www/html/public">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
DirectoryIndex index.php
</Directory>
</VirtualHost>


containerData/apache/setup.sh

/usr/sbin/apache2ctl -D FOREGROUND


Die Shopware Installation muss im sw/ Verzeichnis abgelegt werden.

Die 6.5 Version wird eine aktuelle MySQL-Version enthalten und hoffentlich dann ein komplettes Shopware-Setup über Composer.

Older posts:

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