Besser entwickeln mit Magerun - Teil 4: Entwicklung eines eigenen Magerun-Moduls

Wir haben mittlerweile eine Menge über Magerun erfahren, das von Christian Münch ins Leben gerufene Terminal-Tool für die Administration eines Magento-Shops. Magerun erspart uns Entwicklern Zeit und erleichtert Arbeitsschritte. Hin und wieder fällt einem aber auf, dass sich bestimmte Schritte bei der Entwicklung wiederholen, für die noch keine Abkürzung mit Magerun existiert. Schritte, die auf Dauer so von einigen Minuten auf Stunden aufsummieren können. Und es wäre doch wunderbar, wenn man diese Schritte zeitsparend mit einem Befehlsaufruf erledigen könnte.

Die Lösung: Ein eigenes Magerun-Modul muss her!

Aufgabe: Upgrade-Skript für statischen Block

Wir benötigen des Öfteren Upgrade-Skripte für statische Blöcke. Doch jedes Mal in den letzten Skripten nach der korrekten Struktur zu suchen, ist mühselig. Also legen wir uns in Magerun ein Modul an, dass uns ein fertiges Template ausgibt, so dass wir es später nur noch in unseren Shop übertragen müssen. Von Vorteil wäre noch, wenn der Befehl Multistore-fähig wäre und man Argumente, wie beispielsweise den Block-Identifier, mitgeben kann.

Da die Erstellung und Aktualisierung eines statischen Blocks nahezu identisch verläuft, überprüfen wir im Skript die Existenz des Blocks und erstellen diesen einfach, wenn er nicht existiert.

Wir halten fest:

  • Ausgabe eines Templates für die Erstellung eines statischen Blocks
  • Verarbeiten von folgenden Argumenten:
    → StoreId (optional, default = 0)
    → Identifier
  • Erstellen und aktualisieren von statischen Blöcken

1. Modulstruktur

Magerun sucht beim Aufruf im aktuellen Benutzerverzeichnis nach einem bestimmten Verzeichnis, um die eventuell darin enthaltenen Module mit anzuzeigen.

Unter *nix-Systemen erstellt ihr im Home-Verzeichnis folgende Verzeichnisstruktur:

~/.n98-magerun/modules/<vendor>

Bei Windows kann dies ebenfalls im Benutzer-Verzeichnis angelegt werden:

%userprofile%\n98-magerun\modules\<vendor>

Als "<vendor>" gebt ihr entweder euer Firmenkürzel, den Firmennamen oder eine Produktbezeichnung an. Im Vendor erstellen wir noch unsere Konfigurationsdatei n98-magerun.yaml. Zum Inhalt dieser Datei kommen wir im nächsten Abschnitt.

Der weitere Aufbau sieht wie folgt aus:

modules
  |-- test-modul
   |-- n98-magerun.yaml
    |-- src
     |-- MyNamespace
      |-- MyCommand.php  

Wir ersetzen die Struktur auf folgende Weise:

modules
  |-- tudock
   |-- n98-magerun.yaml
    |-- src
     |-- Tudock
      |-- Cms
       |-- Block
        |-- Update.php

2. Konfigurationsdatei

Anhand der eben erstellten Struktur können wir nun unsere YAML-Datei erstellen.
Magerun definiert die Datei so:

 autoloaders:
  MyNamespace: %module%/src
commands:
  customCommands:
   - MyNamespace\MyCommand

Ersetzen wir die Angaben für unser Beispiel, erhalten wir diese YAML-Datei:

# File: n98-magerun.yaml

autoloaders:
  Tudock: %module%/src
commands:
  customCommands:
   - Tudock\Cms\Block\Update

Magerun ersetzt %module% mit unserem aktuellen Modul-Pfad, somit beginnt unser Modulaufruf über Magerun mit:

magerun tudock

Der gesamte Befehlsaufruf muss mit dem Namespace, wie im Knoten "autoloaders", beginnen.

magerun tudock:cms:block:update

Die Vorbereitung stimmt. Jetzt kommt der Code!

3. Einen eigenen Magerun-Befehl erstellen

Anhand unserer Struktur erstellen wir uns die Klasse Update.php in

.n98-magerun/modules/tudock/src/Tudock/Cms/Block/Update.php

Im Kopf der Datei definieren wir unseren Namespace. Zu allererst benötigen wir die abstrakte Klasse AbstractMagentoCommand, um mit Magento kommunizieren zu können. Anschließend folgen noch drei Interfaces, die wir mit den Symfony-Komponenten einbinden. Die Komponenten benötigen wir zur Ein- und Ausgabe auf der Konsole und zur Übergabe der Argumente, wie beispielsweise unseren Identifier.

# File: Update.php

namespace Tudock;

use N98\Magento\Command\AbstractMagentoCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

Unsere Klasse beginnt mit der configure() Methode, in der wir unseren Befehl benennen, eine Beschreibung geben und benötigte Argumente definieren können. Diese Methode wird von Magerun immer automatisch aufgerufen.

# File: Update.php

class Update extends AbstractMagentoCommand
{
  protected function configure()
 {
   $this
    ->setName('tudock:cms:block:update')
    ->addArgument(
      'identifier',
      InputArgument::REQUIRED,
      'Identifier like footer_links'
    )
    ->addArgument(
      'storeId',
      InputArgument::OPTIONAL,
      'Identifier for specific storeview - default = 0'
    )
    ->setDescription(
      'Create upgrade script for updating/creating a cms block'
    );
  }
...

Im Grunde kann unser Konstrukt noch gar nichts, aber wenn wir uns jetzt auf die Konsole begeben und dort einmal Magerun aufrufen, taucht unser neuer Befehl in der Liste der möglichen Magerun-Befehle auf. Der Befehl besitzt die angegebene Beschreibung. Wir können sogar noch einen Schritt weiter gehen und den Befehl starten:

magerun tudock:cms:block:update

Als Antwort erhalten wir zwar eine RuntimeException mit der Meldung, dass wir nicht genügend Argumente übergeben haben, aber wir sehen trotzdem unsere zusätzlich definierten Argumente:

tudock:cms:block:update <identifier> [<storeId>]

Das Argument identifier ist als erforderlicher Wert angezeigt, während die StoreId als optionaler Wert dargestellt ist.

Ein neuer Versuch mit Argumenten zeigt ein erwartetes Ergebnis:

tudock:cms:block:update footer_links

[LogicException]
You must override the execute() method in the concrete command class.

Einen Fehler. Aber immerhin funktioniert alles soweit schon. Magerun möchte als nächstes die execute() Methode überschrieben haben. Gut, denn diese Methode ist das Kernstück. Die execute() Methode führt unseren Befehl aus der Konsole aus.

Unsere Methode ist nicht allzu groß. Wir behelfen uns erstens mit einer zusätzlichen Methode, die uns einen CMS-Block liefert. Zweitens mit einem Helper, der uns das gesamte Template liefert, welches wir nur noch in ein neues Update-Skript überführen müssen.

# File: Update.php

protected function execute(InputInterface $input, OutputInterface $output)
{
  $this->detectMagento($output);
  if (!$this->initMagento()) {
   return;
  }

  try {
   $identifier = $input->getArgument('identifier');
   $storeId    = $input->getArgument('storeId');

   $block      = $this->getBlock($identifier, $storeId);

   if (!in_array('identifier', $block->getData())) {
    $block->setData('identifier', $identifier);
   }

   $code       = Helper\Generator::generateCode($block);

   $output->writeln($code);

  } catch (\Exception $e) {
   $output->writeln('<error>' . $e->getMessage() . '</error>');
  }
}
protected function getBlock($identifier, $storeId = 0)
{
  $block = $this->_getModel('cms/block', 'Mage_Cms_Model_Block')
   ->setStoreId($storeId)
   ->load($identifier);

  return $block;
}

In den ersten vier Zeilen der execute() Methode versucht Magerun, das Root-Verzeichnis des Magento-Shops zu finden. Anschließend wird Magento initialisiert, damit im folgenden try-catch-Block die gewünschten Anweisungen ausgeführt werden können.

Im try-Block sammeln wir die übergebenen Argumente ein. Ihr wollt wissen, warum wir einfach die Argumente abfragen und an die nächste Methode weiterreichen ohne zu verifizieren, ob da irgendein Wert ankommt? Magerun bzw. Symfony erledigt dies für uns. In der configure() Methode haben wir uns entschieden, dass das Argument identifier ein erforderlicher Wert ist. Fehlt diese Angabe, gelangen wir gar nicht erst in die execute() Methode und brauchen folglich auch keine Verifizierung vorzunehmen.

Als nächstes übergeben wir die Werte identifier und ggf. StoreId an die getBlock() Methode, um einen existierenden Block zu erhalten. Damit wir später in unserem Template nicht erneut den Identifier eingeben müssen, speichern wir diesen in das zuvor erhaltene Block-Objekt – aber nur unter der Voraussetzung, dass der abgefragte Block nicht existiert. Das bedeutet, wir erhalten ein Template, mit dem wir uns einen neuen Block anlegen können.

Den Code, den wir in unserer Helper-Klasse Helper\Generator.php erstellen, stelle ich euch erst zum Schluss des Beitrags zur Verfügung. Im Grunde stellt er nämlich nur ein Template für ein Magento Upgrade-Skript dar, dass mit einigen Variablen befüllt wird, aber nichts mehr mit der Erstellung des Magerun-Moduls zu tun hat.

Am Ende des try-Blocks geben wir das erhaltene Template mit der Methode writeln() wieder in der Konsole aus. Ihr könnt das Template auch in eine Datei schreiben.

Dafür hängt ihr an den Magerun Befehl noch Folgendes an:

magerun tudock:cms:block:update identifier > upgrade-0.0.0.1-0.0.0.2.php

Damit sind wir am Ende des vierten Beitrags. Ich hoffe, ihr habt Verwendung für dieses kleine Modul und konntet den Schritten folgen. Wenn ihr Verbesserungsvorschläge habt oder eine Idee für andere Module, dann schreibt es gerne unten in den Kommentar.

Das Modul lässt sich übrigens auch prima für die Erstellung beziehungsweise Aktualisierung von CMS-Seiten verwenden.

 

# File: Generator.php

 

<?php

namespace Tudock\Cms\Block\Helper;

class Generator
{
   

    /**
    
* Generate output code
     *
     * @param \Mage_Cms_Model_Block $block Model Block
     *
     * @return string
     */
    public static function generateCode($block, $storeId)
    {
        if (empty($storeId)) {
            $storeId = 0;
        }
       
        //generate script using simple string concatenation
        $script = <<<EOF
<?php
       
    Mage::log(
        'upgrade script is starting for block "{$block->getIdentifier()}"',
        null,
        'upgrade.log',
        true
    );
   
    \$installer = \$this;
    \$installer->startSetup();
   
    \$identifier = '{$block->getIdentifier()}';
    \$title      = '{$block->getTitle()}';

EOF;

        $script .= "
   
\$contentCmsBlock = <<<EOT
{$block->getContent()}
EOT;";
   
        $script .= <<<EOF


    /** @var Mage_Cms_Model_Resource_Block_Collection \$collection */
    \$collection = Mage::getModel('cms/block')
        ->getCollection();
    \$collection->addStoreFilter({$storeId})
        ->addFieldToFilter('identifier', \$identifier);
   
    /**  */
    if (\$collection->getSize() > 0 ){
        \$cmsBlock = \$collection->getFirstItem();
    } else {
        \$cmsBlock = Mage::getModel('cms/block');
    }
   
    if (!\$cmsBlock->isObjectNew()) {
        \$cmsBlock->setTitle(\$title)
            ->setContent(\$contentCmsBlock)
            ->setStores({$storeId})
            ->setIsActive(true);
       
        try {
            \$cmsBlock->save();
            
            Mage::log(
                '{$block->getIdentifier()} CMS block updated',
                null,
                'upgrade.log',
                true
            );
        } catch (\Exception \$e) {
            Mage::log(
                \$e->getMessage(),
                null,
                'upgrade.log',
                true
            );
        }
    } else {
        \$cmsBlock->load();
        \$stores = \$cmsBlock->getStores();
       
        if (!in_array(array({$storeId}), \$stores)){
            \$stores = [{$storeId}];
        }
       
        \$cmsBlock->setTitle(\$title)
            ->setContent(\$contentCmsBlock)
            ->setStores(\$stores)
            ->setIsActive(true);
       
        try {
            \$cmsBlock->save();
           
            Mage::log(
                'footer_links CMS block updated',
                null,
                'upgrade.log',
                true
            );
        } catch( \Exception \$e ) {
            Mage::log(
                \$e->getMessage(),
                null,
                'upgrade.log',
                true
            );
        }
    }
EOF;
       
        return $script;
    }
}

Nachtrag: Diesen Beitrag habe ich aufgrund eines Tippfehlers (Entschuldigung!) im obigen Script am 29.03.2017 bearbeitet.

Autor: Kategorie: Webentwicklung
Schlagwörter: Magento, Wissen
YTo2OntzOjY6ImZldXNlciI7aTowO3M6MzoicGlkIjtpOjk3O3M6MzoiY2lkIjtpOjEwOTcxMjQyODA7czo0OiJjb25mIjthOjEwOntzOjEwOiJzdG9yYWdlUGlkIjtpOjEyNDtzOjE0OiJleHRlcm5hbFByZWZpeCI7czoxMToidHhfbmV3c19waTEiO3M6MTM6IlVzZXJJbWFnZVNpemUiO2k6MTg7czo5OiJhZHZhbmNlZC4iO2E6MTp7czoyMToic2hvd0NvdW50Q29tbWVudFZpZXdzIjtpOjA7fXM6ODoic2hhcmluZy4iO2E6MTp7czoxMjoidXNlU2hhcmVJY29uIjtOO31zOjg6InJhdGluZ3MuIjthOjM6e3M6MTY6InJhdGluZ0ltYWdlV2lkdGgiO2k6MTE7czoxNjoicmV2aWV3SW1hZ2VXaWR0aCI7aToxNjtzOjg6Im1vZGVwbHVzIjtzOjQ6ImF1dG8iO31zOjY6InRoZW1lLiI7YTo3OntzOjI2OiJib3htb2RlbFRleHRhcmVhTGluZUhlaWdodCI7aTozMDtzOjI0OiJib3htb2RlbFRleHRhcmVhTmJyTGluZXMiO2k6NDtzOjE1OiJib3htb2RlbFNwYWNpbmciO2k6MTA7czoxODoiYm94bW9kZWxMaW5lSGVpZ2h0IjtpOjIwO3M6MTg6ImJveG1vZGVsTGFiZWxXaWR0aCI7aToxMzQ7czoyNjoiYm94bW9kZWxMYWJlbElucHV0UHJlc2VydmUiO2k6MTtzOjIyOiJib3htb2RlbElucHV0RmllbGRTaXplIjtpOjM1O31zOjE3OiJwcmVmaXhUb1RhYmxlTWFwLiI7YToxMDp7czoxNDoidHhfYWxidW0zeF9waTEiO3M6MTc6InR4X2FsYnVtM3hfaW1hZ2VzIjtzOjE1OiJ0eF9jb21tZXJjZV9waTEiO3M6MjA6InR4X2NvbW1lcmNlX3Byb2R1Y3RzIjtzOjEyOiJ0eF9pcmZhcV9waTEiO3M6MTA6InR4X2lyZmFxX3EiO3M6MTU6InR4X21pbmluZXdzX3BpMSI7czoxNjoidHhfbWluaW5ld3NfbmV3cyI7czo5OiJ0eF90dG5ld3MiO3M6NzoidHRfbmV3cyI7czoxMToidHRfcHJvZHVjdHMiO3M6MTE6InR0X3Byb2R1Y3RzIjtzOjI0OiJ0eF93ZWNzdGFmZmRpcmVjdG9yeV9waTEiO3M6MjU6InR4X3dlY3N0YWZmZGlyZWN0b3J5X2luZm8iO3M6MTI6InR4X2NvbW11bml0eSI7czo4OiJmZV91c2VycyI7czoxOToidHhfY3d0Y29tbXVuaXR5X3BpMSI7czo4OiJmZV91c2VycyI7czoxMToidHhfbmV3c19waTEiO3M6MjU6InR4X25ld3NfZG9tYWluX21vZGVsX25ld3MiO31zOjExOiJzaG93VWlkTWFwLiI7YTo1OntzOjk6InR4X3R0bmV3cyI7czo3OiJ0dF9uZXdzIjtzOjExOiJ0dF9wcm9kdWN0cyI7czo3OiJwcm9kdWN0IjtzOjEyOiJ0eF9jb21tdW5pdHkiO3M6NDoidXNlciI7czoxOToidHhfY3d0Y29tbXVuaXR5X3BpMSI7czoyNToiYWN0aW9uPWdldHZpZXdwcm9maWxlJnVpZCI7czoxMToidHhfbmV3c19waTEiO3M6NDoibmV3cyI7fXM6MTI6IlJlcXVpcmVkTWFyayI7czoxOiIqIjt9czo0OiJsYW5nIjtzOjI6ImRlIjtzOjM6InJlZiI7czoyOToidHhfbmV3c19kb21haW5fbW9kZWxfbmV3c18yODAiO30%3DYTo0OntzOjQ6ImNvbmYiO2E6MzU6e3M6MTc6InVzZVdlYnBhZ2VQcmV2aWV3IjtzOjE6IjEiO3M6MjI6InVzZVdlYnBhZ2VWaWRlb1ByZXZpZXciO3M6MToiMSI7czoyMDoid2VicGFnZVByZXZpZXdIZWlnaHQiO3M6MjoiNzAiO3M6MjA6Im1heENoYXJzUHJldmlld1RpdGxlIjtzOjI6IjcwIjtzOjMxOiJ3ZWJwYWdlUHJldmlld0Rlc2NyaXB0aW9uTGVuZ3RoIjtzOjM6IjE2MCI7czozODoid2VicGFnZVByZXZpZXdEZXNjcmlwdGlvbk1pbmltYWxMZW5ndGgiO3M6MjoiNjAiO3M6Mjc6IndlYnBhZ2VQcmV2aWV3Q2FjaGVUaW1lUGFnZSI7czozOiIxODAiO3M6MzM6IndlYnBhZ2VQcmV2aWV3Q2FjaGVUaW1lVGVtcEltYWdlcyI7czoyOiI2MCI7czozMDoid2VicGFnZVByZXZpZXdDYWNoZUNsZWFyTWFudWFsIjtzOjE6IjAiO3M6Mjg6IndlYnBhZ2VQcmV2aWV3TnVtYmVyT2ZJbWFnZXMiO3M6MjoiMTAiO3M6Mzg6IndlYnBhZ2VQcmV2aWV3U2Nhbk1pbmltYWxJbWFnZUZpbGVTaXplIjtzOjQ6IjE1MDAiO3M6MzA6IndlYnBhZ2VQcmV2aWV3U2Nhbk1pbkltYWdlU2l6ZSI7czoyOiI0MCI7czozMDoid2VicGFnZVByZXZpZXdTY2FuTWF4SW1hZ2VTaXplIjtzOjM6IjQ1MCI7czoyOToid2VicGFnZVByZXZpZXdTY2FuTWluTG9nb1NpemUiO3M6MjoiMzAiO3M6MzE6IndlYnBhZ2VQcmV2aWV3U2Nhbk1heEltYWdlU2NhbnMiO3M6MjoiNDAiO3M6Mzg6IndlYnBhZ2VQcmV2aWV3U2Nhbk1heEltYWdlU2NhbnNGb3JMb2dvIjtzOjI6IjU1IjtzOjQwOiJ3ZWJwYWdlUHJldmlld1NjYW5NYXhIb3J6aXpvbnRhbFJlbGF0aW9uIjtzOjI6IjU7IjtzOjM3OiJ3ZWJwYWdlUHJldmlld1NjYW5tYXh2ZXJ0aWNhbHJlbGF0aW9uIjtzOjE6IjMiO3M6MzA6IndlYnBhZ2VQcmV2aWV3U2NhbkxvZ29QYXR0ZXJucyI7czoxMDoibG9nbyxjcmdodCI7czozODoid2VicGFnZVByZXZpZXdTY2FuRXhjbHVkZUltYWdlUGF0dGVybnMiO3M6NTc6InBpeGVsdHJhbnMsc3BhY2VyLHlvdXR1YmUscmNsb2dvcyx3aGl0ZSx0cmFuc3BhLGJnX3RlYXNlciI7czozODoid2VicGFnZVByZXZpZXdEZXNjcmlwdGlvblBvcnRpb25MZW5ndGgiO3M6MjoiNDAiO3M6MjU6IndlYnBhZ2VQcmV2aWV3Q3VybFRpbWVvdXQiO3M6NDoiNzAwMCI7czoxMjoidXNlUGljVXBsb2FkIjtzOjE6IjAiO3M6MTI6InVzZVBkZlVwbG9hZCI7czoxOiIwIjtzOjEzOiJwaWNVcGxvYWREaW1zIjtzOjM6IjEwMCI7czoxNjoicGljVXBsb2FkTWF4RGltWCI7czozOiI4MDAiO3M6MTY6InBpY1VwbG9hZE1heERpbVkiO3M6MzoiOTAwIjtzOjIyOiJwaWNVcGxvYWRNYXhEaW1XZWJwYWdlIjtzOjM6IjQ3MCI7czoyMzoicGljVXBsb2FkTWF4RGltWVdlYnBhZ2UiO3M6MzoiMzAwIjtzOjIwOiJwaWNVcGxvYWRNYXhmaWxlc2l6ZSI7czo0OiIyNTAwIjtzOjIwOiJwZGZVcGxvYWRNYXhmaWxlc2l6ZSI7czo0OiIzMDAwIjtzOjE4OiJzb3VuZGNsb3VkQ2xpZW50SUQiO3M6MDoiIjtzOjIyOiJzb3VuZGNsb3VkQ2xpZW50U2VjcmV0IjtzOjA6IiI7czoyMDoidXNlVG9wV2VicGFnZVByZXZpZXciO3M6MDoiIjtzOjI0OiJ0b3BXZWJwYWdlUHJldmlld1BpY3R1cmUiO2k6MDt9czoxMToiYXdhaXRnb29nbGUiO3M6Mjg6IldhcnRlIGF1ZiBBbnR3b3J0IHZvbiBHb29nbGUiO3M6ODoidHh0aW1hZ2UiO3M6MTM6IkJpbGQgZ2VmdW5kZW4iO3M6OToidHh0aW1hZ2VzIjtzOjE1OiJCaWxkZXIgZ2VmdW5kZW4iO30%3DYTowOnt9YTowOnt9YTo3OntzOjE2OiJjb21tZW50TGlzdEluZGV4IjthOjE6e3M6MzI6ImNpZHR4X25ld3NfZG9tYWluX21vZGVsX25ld3NfMjgwIjthOjE6e3M6MTA6InN0YXJ0SW5kZXgiO2k6MTU7fX1zOjE0OiJjb21tZW50c1BhZ2VJZCI7aTo5NztzOjE2OiJjb21tZW50TGlzdENvdW50IjtpOjEwOTcxMjQyODA7czoxMjoiYWN0aXZlbGFuZ2lkIjtpOjA7czoxNzoiY29tbWVudExpc3RSZWNvcmQiO3M6Mjk6InR4X25ld3NfZG9tYWluX21vZGVsX25ld3NfMjgwIjtzOjEyOiJmaW5kYW5jaG9yb2siO3M6MToiMCI7czoxMjoibmV3Y29tbWVudGlkIjtOO30%3D YTo1OntzOjExOiJleHRlcm5hbFVpZCI7aToyODA7czoxMjoic2hvd1VpZFBhcmFtIjtzOjQ6Im5ld3MiO3M6MTY6ImZvcmVpZ25UYWJsZU5hbWUiO3M6MjU6InR4X25ld3NfZG9tYWluX21vZGVsX25ld3MiO3M6NToid2hlcmUiO3M6MTcwOiJhcHByb3ZlZD0xIEFORCBleHRlcm5hbF9yZWY9J3R4X25ld3NfZG9tYWluX21vZGVsX25ld3NfMjgwJyBBTkQgcGlkPTEyNCBBTkQgdHhfdG9jdG9jX2NvbW1lbnRzX2NvbW1lbnRzLmRlbGV0ZWQ9MCBBTkQgdHhfdG9jdG9jX2NvbW1lbnRzX2NvbW1lbnRzLmhpZGRlbj0wIEFORCBwYXJlbnR1aWQ9MCI7czoxMDoid2hlcmVfZHBjayI7czoxMzk6ImV4dGVybmFsX3JlZj0ndHhfbmV3c19kb21haW5fbW9kZWxfbmV3c18yODAnIEFORCBwaWQ9MTI0IEFORCB0eF90b2N0b2NfY29tbWVudHNfY29tbWVudHMuZGVsZXRlZD0wIEFORCB0eF90b2N0b2NfY29tbWVudHNfY29tbWVudHMuaGlkZGVuPTAiO30%3D YToyOntpOjA7czoxNDk6IjxpbWcgc3JjPSIvdHlwbzNjb25mL2V4dC90b2N0b2NfY29tbWVudHMvcmVzL2Nzcy90aGVtZXMvdHVkb2NrL2ltZy9wcm9maWxlLnBuZyIgY2xhc3M9InR4LXRjLXVzZXJwaWMgdHgtdGMtdWltZ3NpemUiIHRpdGxlPSIiICBpZD0idHgtdGMtY3RzLWltZy0iIC8%2BIjtpOjk5OTk5O3M6MTUxOiI8aW1nIHNyYz0iL3R5cG8zY29uZi9leHQvdG9jdG9jX2NvbW1lbnRzL3Jlcy9jc3MvdGhlbWVzL3R1ZG9jay9pbWcvcHJvZmlsZWYucG5nIiBjbGFzcz0idHgtdGMtdXNlcnBpY2YgdHgtdGMtdWltZ3NpemUiIHRpdGxlPSIiICBpZD0idHgtdGMtY3RzLWltZy0iIC8%2BIjt9
Bitte bestätigen Sie
Nein
Ja
Information
Ok
Vorschau wird geladen ...
* Pflichtfeld
Ihr Kommentar ist eine Antwort auf den folgenden Kommentar

Wir behalten uns vor, Kommentare zu löschen, beispielsweise wenn sich diese nicht auf den Beitrag beziehen, zur Eigenwerbung missbraucht werden, persönliche Daten anderer enthalten, diskriminieren, beleidigen oder Rechte verletzen.

Datenschutzhinweis: Wenn Sie einen Kommentar oder sonstigen Beitrag in unserem Blog hinterlassen, speichern wir neben Ihren Angaben Ihre IP-Adresse. Darüber hinaus können Sie die Beiträge und Kommentare unseres Blogs abonnieren. Das Kommentarabonnement können Sie jederzeit abbestellen. Weitere Informationen finden Sie in unserer Datenschutzerklärung.

E-Commerce & Webentwicklung: Wir realisieren und optimieren Internetprojekte. Unser Fachgebiet sind Onlineshops, Rich Internet Applications und anspruchsvolle Onlineauftritte. Über unsere Arbeit schreiben wir. Hier im Blog von TUDOCK.