Zum Inhalt

Hinweise, Tipps & Tricks

Es gibt einige Möglichkeiten, um die Entwicklung von Plugins für den JTL-Shop zu vereinfachen.

Konstanten

Im ersten Schritt können in der [Shop-Root]/includes/config.JTL-Shop.ini.php eigene Konstanten definiert werden:

//Plugin-Devmodus aktivieren. Nach Änderungen an der info.xml muss das Plugin nicht mehr deinstalliert und
//neu installiert werden, außerdem zusätzliche Optionen in der Shop-CLI
const PLUGIN_DEV_MODE = true;

//backtrace bei SQL-Exceptions auslösen
const NICEDB_EXCEPTION_BACKTRACE = true;

//Backtrace ggf. via echo im Frontend ausgeben
const NICEDB_EXCEPTION_ECHO = true;

//alle durch PHP verursachten Fehler, Warnungen und Hinweise im Frontend anzeigen
const SHOP_LOG_LEVEL = E_ALL;

//alle Fehler, Warnungen und Hinweise bei Wawi-Abgleich anzeigen
const SYNC_LOG_LEVEL = E_ALL;

//alle Fehler, Warnungen und Hinweise im Backend anzeigen
const ADMIN_LOG_LEVEL = E_ALL;

//alle Fehler, Warnungen und Hinweise in Templates anzeigen
const SMARTY_LOG_LEVEL = E_ALL;

//Smarty-Templates bei jedem Seitenaufruf neu kompilieren
const SMARTY_FORCE_COMPILE = true;

Abgleich mit JTL-Wawi

Falls zu Debug-Zwecken der Abgeich mit JTL-Wawi näher untersucht werden soll, lässt sich das Löschen der übertragenen XML-Dateien folgendermaßen verhindern:

const KEEP_SYNC_FILES = true;

XHProf / Tideways

Falls XHProf oder Tideways auf dem Server installiert sind, kann über die Konstante

const PROFILE_SHOP = true;

auch der gesamte Code des Onlineshops analysiert werden.

SQL-Queries

Sämtliche über die NiceDB-Klasse ausgeführten SQL-Queries können via

const PROFILE_QUERIES = true;

im Profiler gespeichert werden. Unter "Profiler" sind sie anschließend im Tab "SQL" zu sehen.

Alternativ lassen sie sich via

const PROFILE_QUERIES_ECHO = true;

auch direkt im Frontend anzeigen.

In beiden Fällen kann der Informationsgehalt über

//verbosity level. 0-3
const DEBUG_LEVEL = 0;

gesteuert werden. Je höher der Wert, desto mehr Informationen werden gespeichert bzw. ausgegeben.

Migration von Plugin-Tabellen auf den Shop-Standard

Mit der Version 5.3 von JTL-Shop wurden alle Shop-Tabellen auf einen 4-Byte-UTF8-Zeichensatz umgestellt. Bei der automatischen Migration werden Plugin-Tabellen nicht berücksichtigt. Hier müssen Plugin-Entwickler in einer eigenen Migration für eine Umstellung sorgen. Am einfachsten kann die Umstellung mittels der Methode JTL\Update\DBMigrationHelper::migrateToInnoDButf8() erfolgen.

//Migration20231231123456.php
<?php
declare(strict_types=1);

namespace Plugin\meinePluginID\Migrations;

use JTL\Plugin\Migration;
use JTL\Update\DBMigrationHelper;
use JTL\Update\IMigration;

/**
 * Class Migration20231231123456
 * @package Plugin\meinePluginID\Migrations
 */
class Migration20231231123456 extends Migration implements IMigration
{
    /**
     * @inheritDoc
     */
    public function up()
    {
        DBMigrationHelper::migrateToInnoDButf8('meinePluginTabelle');
    }

    ...
}

Checksumme für den Warenkorb

Mit der Version 4.05 von JTL-Shop wurde im Warenkorb eine Checksumme zur Prüfung auf Konsistenz eingeführt ("Breaking Change"). Mit dieser Prüfung soll verhindert werden, dass während der Anzeige der Bestellzusammenfassung für den Kunden im Hintergrund Änderungen an den gekauften Artikeln durchgeführt werden, die dem Kunden nicht angezeigt werden. Solche Änderungen könnten z. B. Preisänderungen durch einen Abgleich mit JTL-Wawi oder parallele Abverkäufe sein.

Eine solche Änderung wird durch den Vergleich der Prüfsumme direkt vor dem Speichern der Bestellung mit der Meldung quittiert:

Ihr Warenkorb wurde aufgrund von Preis- oder Lagerbestandsänderungen aktualisiert.
Bitte prüfen Sie die Warenkorbpositionen.

Der Kunde wird dann zurück zum Warenkorb geleitet.

Important

Ein Plugin, das direkt den Warenkorb manipuliert (um z. B. einen speziellen Rabatt einzufügen), muss selbst dafür sorgen, die Prüfsumme nach den eigenen Änderungen zu aktualisieren, damit die Bestellung nicht in einer Schleife endet.

Die Aktualisierung erfolgt durch den statischen Aufruf der Methode refreshChecksum() der Klasse \JTL\Cart\Cart mit dem aktuellen Warenkorb als Parameter.

Warenkorb::refreshChecksum($_SESSION['Warenkorb']);

Registry

Eine simple Registry zum Speichern von beliebigen Werten innerhalb eines Requests kann über die Shop-Klasse erreicht werden. Hierfür sind die Funktionen Shop Shop::get(string $key) zum Auslesen, bool Shop::has(string $key) zum Prüfen sowie mixed Shop::set(string $key, mixed $value) zum Setzen vorhanden.

Beispiel:

//file1.php
Shop::set('my-plugin-var01', ['foo' => 'bar']);

//file2.php, später aufgerufen
$test  = Shop::has('my-plugin-var01'); //TRUE
$data  = Shop::get('my-plugin-var01'); //array('foo' => 'bar')
$test2 = Shop::has('NOT-my-plugin-var01'); //FALSE

SQL

Es wird dringend geraten, die Funktionen NiceDB::insert(), NiceDB::delete() und NiceDB::update() anstelle von NiceDB::executeQuery() zu nutzen. Nur diese Varianten nutzen Prepared Statements!

Im Object-Kontext, wird auf diese Methoden nicht mehr direkt und statisch zugegriffen, sondern via Dependency Injection Container. Ein Beispiel sehen Sie hier:

class Example
{
   protected $dbHandler;

   public function __constructor()
   {
       $this->dbHandler = Shop::Container()->getDB();
       $this->dbHandler->select(/*...*/);
   }
}

Selektieren einzelner Zeilen

Insbesondere bei der Behandlung von Nutzereingaben ist es fahrlässig, unbehandelte POST- oder GET-Parameter direkt in SQL-Queries zu integrieren!

Negativ-Beispiel:

$row = Shop::Container()->getDB()->executeQuery("SELECT * FROM my_table WHERE id = " . $_POST['id'], 1);

Falls es sich bei der Spalte id um einen numerischen Datentyp handelt, sollte zumindest ein Datentyp-Casting vorgenommen werden, z. B. mittels (int)$_POST['id'].

Der präferierte Weg wäre jedoch die Nutzung der Methode NiceDB::selectSingleRow().

Das obige "Negativ-Beispiel" ließe sich damit wie folgt umschreiben:

Positiv-Beispiel:

$result = Shop::Container()->getDB()->select('my_table', 'id', (int)$_POST['id']);

Hint

Shop::Container()->getDB()->query() ist analog zu Shop::Container()->getDB()->query($sql, 1) mit zweitem Parameter auf "1" gesetzt, was für "single fetched object" steht.

Hierbei sind allerdings nur einfache WHERE-Bedingungen mit AND-Verknüpfungen möglich.

Einfügen von Zeilen

Analog zum Selektieren ein Beispiel mit einem Insert:

Unsichere Variante:

$i = Shop::Container()->getDB()->executeQuery("
    INSERT INTO my_table
        ('id', 'text', 'foo')
        VALUES (" . $_POST['id'] . ", '" . $_POST['text'] . "', '" . $_POST['foo'] . "')", 3
);

Bessere Variante:

$obj       = new stdClass();
$obj->id   = (int) $_POST['id'];
$obj->text = $_POST['text'];
$obj->foo  = $_POST['foo'];
$i = Shop::Container()->getDB()->insert('my_table', $obj);

Löschen von Zeilen

Unsichere Variante:

Shop::Container()->getDB()->executeQuery("
    DELETE FROM my_table
        WHERE id = " . $_POST['id'], 3
);

Bessere Variante:

Shop::Container()->getDB()->delete('my_table', 'id', (int) $_POST['id']);

Bei erweiterten WHERE-Klauseln mit AND-Bedingung können zwei Arrays mit jeweils allen Keys und allen Values übergeben werden:

Shop::Container()->getDB()->delete('my_table', array('id', 'foo'), array(1, 'bar'));
// --> DELETE FROM my_table WHERE id = 1 AND 'foo' = 'bar'

Aktualisieren von Zeilen

Unsichere Variante:

Shop::Container()->getDB()->executeQuery("
    UPDATE my_table
        SET id = " . $_POST['new_id'] . ",
            foo = '" . $_POST['foo'] . "',
            bar = 'test'
        WHERE id = " . $_POST['id'], 3
);

Bessere Variante:

$obj      = new stdClass();
$obj->id  = (int) $_POST['new_id'];
$obj->foo = $_POST['foo'];
$obj->bar = 'test';
Shop::Container()->getDB()->update('my_table', 'id', (int) $_POST['id'], $obj);

Important

Sollte es nicht möglich sein, die beschriebenen Methoden zu nutzen, so sollten sämtliche potentiell gefährlichen Werte über Shop::Container()->getDB()->escape() zuvor maskiert, bzw. im Fall von Numeralen konvertiert, werden.

Tipps

  • smarty->assign() kann gechaint werden:
$smarty->assign('var_1', 1)
       ->assign('var_2', 27)
       ->assign('var_3', 'foo');
  • Die Klasse Shop bildet einen zentralen Einstiegspunkt für häufig verwendete Funktionalitäten:
Shop::Container()->getCache()->flushAll(); //Objektcache leeren

$arr = Shop::Container()->getDB()->query($sql, 2);

$translated = Shop::Lang()->get('newscommentAdd', 'messages');

$shopURL = Shop::getURL(); //statt URL_SHOP, prüft auf SSL

$conf = Shop::getSettings([CONF_GLOBAL, CONF_NEWS]);

Shop::dbg($someVariable, false, 'Inhalt der Variablen:'); //Schnelles Debugging

$smarty = Shop::Smarty(); //Alias für globales Smarty-Objekt

Shop::set('my_key', 42); //Registry-Setter

$value = Shop::get('my_key'); //Registry-Getter - 42

$hasValue = Shop::has('some_other_key'); //Registry-Prüfung - false