Problembeschreibung

Immerhin hat der JTL-Shop schon diverse CMS-typische Funktionen, mit denen es auch möglich ist an Seitenpositionen ergänzende Informationen als Blöcke anzuzeigen. Diese Blöcke werden in JTL "Boxen" genannt. Dazu gibt es ein Set an Startseitenboxen die templateabhängig (z.B. wie bei EVO) als Produktslider präsentiert werden. Damit werden z.B. solche Funktionen angezeigt wie: "Neueste Produkte", "Beliebtesten Produkte", "Top-Produkte", "Aktionsprodukte" etc. Weiterhin gibt es viele andere Boxen und u.a. auch frei gestaltbare "Custom-Boxen", die beliebig mit Text oder HTML-Inhalten befüllt werden können.

Leider ist es im Template EVO (4.0.6) so, dass für die Startseitenboxen und Custom-Boxen festgelegte und eingeschränkte Ausgabepositionen und Funktionen gibt. In meinen betreuten Projekten zeigte sich hier eine gewisse Unflexibiliät, was die auftraggeberseitig gewünschte Anordnung der Boxen betraf. Da sollte schon mal die Box X mit ihrem Inhalt oberhalb des Seitenhauptinhaltes, aber zwischen den Top-Produkten und den Neuesten Produkten stehen und eine weitere Box unterhalb des Contents, wo dann auch weitere der o.g. Produktslider erscheinen sollten. Also eine beliebige, vermischte und ober- und unterhalb des Seitenhauptinhaltes angegeordnete Anzeige. Das ist mit EVO von hausaus nicht möglich. Nachfolgend beschreibe ich eine Scriptlösung, mit der man dieses Manko beheben kann.

 

Problemlösung

Voraussetzungen

Prinzipiell gehe ich davon aus, Sie wissen, bzw. sind vertraut wie man:

  • wie man Override-Scripte für das EVO-Child-Template erstellt.
  • vom EVO-Template ein eigenes Template-Child erstellt, damit man nicht das Original zermascht, sondern ein sauberes Override-Template hat und auch wie Sie Override-Scripte anlegen, d.h. wie die Verzeichnisstrukturen einzuhalten und anzuwenden sind. Dazu gibt es entsprechende Hilfeseiten bei JTL.
  • eine eigene Template-Konfigurations-XML anlegt (abgespeckte Kopie von templates/Evo/template.xml.nach templates/EvoChild-meinTemplate/template.xml erstellen).
  • in der Shop-Administration über den Menüpunkt Inhalte > Boxenverwaltung eigene Boxen oder Container anlegt.
  • auf die Startseitenboxen-Produktslider per Name zugreift.

Erledigen Sie folgendes:

  • Legen Sie ein leeres Script unter templates/EvoChild-meinTemplate/php/functions.php an.
  • Legen Sie ein Override-Template-Script von page/index.tpl auf  templates/EvoChild-meinTemplate/page/index.tpl und
  • von layout/index.tpl ein Clone unter templates/EvoChild-meinTemplate/layout/index.tpl an.

 

Erläuterungen zu den o.g. Override-Scripten

Die vier Template-Dateien, die für unsere Aufgabenstellung relevant sind:

template.xml Enthält die Configurationsdaten für Ihr Child-Template, welche dann über das Shop-Backend administrierbare Optionen ermöglicht (s. Hauptmenü > Template > meinTemplate > Btn [Einstellungen]).
layout/index.tpl Ist das Haupt-Smarty-Template-Script für das EVO-Child-Template. Hier werden die grundsätzlichen Seitenstrukturen aufgebaut bzw. alle weiteren Untertemplates eingebunden.
Die Hauptstrukturen (Blöcke) sind z.B. header, content und footer. Hier ist also auch die Reihenfolge von Seitenelementen bestimmbar.
page/index.tpl Das ist das wichtige Untertemplate für alle Standard-Shopseiten (in der Seite-Pulldownliste benannt als "Alle Seiten", also ausgenommen z.B. Suche, Sitemap, 404-Seite, Newsletter etc.). Wird z.B. auf der Seite Wir über uns oder Datenschutzerklärung, Zahlungsmöglichkeiten, Versandinformationen etc.
php/functions.php Das ist ein Script, welches es ermöglicht zu den EVO-Grundfunktionen weitere hinzuzufügen oder die Grundfunktionen zu überschreiben.

 

Die Config-Parameter-Datei template.xml

Zunächst brauchen wir für die Anzeige der Boxen eine weitere neue Position, die so im EVO-Template auch schon vorgesehen, aber standardmäßig nicht aktiv ist. Dazu öffnet Sie Ihre template.xml-Kopie templates/EvoChild-meinTemplate/template.xml. (Hinweis: Diese braucht nur die Knoten enthalten, die durch uns Child-Template relevant verändert werden. Alle anderen Optionen werden quasi vom Parent-EVO-Template als geerbt übernommen.) Ganz unten findet man oder sollte eingefügt werden einen <Boxes>-Knoten in dem es eine Element mit der Position="top" gibt/geben sollte. Hier setzen wir Availability="1" und schalten damit diese Box-Position auf verfügbar.
Adäquat sollte es ein Postion bottom geben, die ggf. bei Bedarf eingefügt und/oder aktiviert wird.

<Boxes>
<Container Position="left" Available="1" />
<Container Position="right" Available="0" />
<Container Position="top" Available="1" />
<Container Position="content" Available="1" />
<Container Position="bottom" Available="1" />
</Boxes>

Neben dieser Modifikation fügen wir einen eigenen Satz an Settings mit dem Key productboxpositions ein:

<Settings>
   ...
   <Section Name="Produkt-Box-Positionen" Key="productboxpositions">
       <Setting Description="Reihenfolge Boxen vor dem Inhalt"  Key="boxes_order_above" Type="text" Value="TopAngebot|NeuImSortiment"/>
       <Setting Description="Reihenfolge Boxen nach dem Inhalt" Key="boxes_order_below" Type="text" Value="Bestseller|Sonderangebote"/>
   </Section>
   ...
<Settings>

Damit steht in der Template-Konfiguration im Shop-Backend eine neue Gruppe mit Einstellungen zur Verfügung in die dann unsere Sortierreihenfolge in Form von Boxnamen oder Box-IDNr. getrennt durch Pipes eingetragen werden können. (Anmerkung: Die von mir oben angzeigten Vorbefüllungswerte im Tag-Attribut werden nicht in jedem Fall in der Template-Konfiguration auch gleich angezeigt. Das ist aber nicht schlimm, weil man diese sowie dann in der Template-Konfiguration in die beiden unten sichbarern neuen Boxfelder eintragen. Abhängig von den Name Ihrer Boxen müssen natürlich auch die dortigen Eintragungen abweichen.

Es gibt zwei Parameterfelder, eines in dem alle Boxen mit Namen oder IDNr eingetragen werden, die oberhalb des Contants angezeigt werden sollen und eines für die Anzeige unterhalb des Seiten-Hauptinhalts. In der Reihenfolge wie hier mit Pipe separiert eingetragen wird, erfolgt dann auch die Ausgabe im Frontend.

Absichtlich habe ich hier die Möglichkeit zur Eingabe von ID-Nummern ermöglicht. Boxentitel sind im JTL-Shop nicht unique. D.h. unterschiedlichen Boxen können den gleichen Namen verwenden. Wer dann über die Boxnamen arbeitet, wird evtl. zufällige Ergebnisse erreichen. Dann ist es zuverlässiger mit ID-Nr. zu arbeiten, oder aber Sie stellen sicher, dass Sie keine Namen doppelt vergeben. Startseitenboxen der Produktslider haben immer einen festen unveränderlichen Unique-Namen, so wie oben im Screenshot als Beispieleintrag zu sehen.

Die ID-Nr. für eine Box können Sie feststellen, in dem Sie den URL analysieren, wenn Sie eine Box zur Bearbeitung im JTL-Backend in der Boxenverwaltung öffnen:

 

Hier mal eine Liste mit internen Informationen zu den Startseitenboxen:

s. www.Ihr-JTL-Shop.de/admin/suchspecials.php (1)
s. www.Ihr-JTL-Shop.de/admin/einstellungen.php?kSektion=8 (2)

Produkt-Box (1) Box (2) $Box->Name Script (Vorlage/Template)
Bestseller Bestseller Bestseller boxes/box_bestseller.tpl
Sonderangebot Sonderangebote Sonderangebote boxes/box_special_offer.tpl
Neue im Sortiment Neu im Sortiment NeuImSortiment boxes/box_new_in_stock.tpl
Top Angebot Top Angebot TopAngebot boxes/box_top_offer.tpl
In Kürze verfügbar Bald erscheinende Produkte   boxes/box_coming_soon.tpl
Top bewertet Top Bewertet   boxes/box_top_rated.tpl
  Zuletzt angesehen    
  Preisradar   boxes/box_priceradar.tpl

 

Das Funktionen-Scipt funtions.php

Hierin erstellen wir zwei neue Methoden hole_PositionBoxTitel() und allBoxesOrderedInTargetPositions().

Die erste Funktion ist lediglich eine Hilfsfunktion die durch die zweite genannte Funktion benötigt wird. Der Grund für deren Notwendigkeit ist, dass im JTL-Shop beim Zugriff auf die Boxen hier keine Boxen-Titel übergeben und im Boxen-Objekt bereitgestellt werden. Wenn wir aber z.B. über die Boxen-Titel dann unsere Boxen-Reihenfolgen festlegen wollen, brauchen wir deren Namen für den Zugriff.

Die zweite Funktion macht die Hauptarbeit unserer oben beschriebenen Aufgabenstellung. Sie bekommt alle Startseiten-Produktslider-Boxen und Custom-Boxen übergeben. Anhand der konfigurierten Sortierreihenfolge werden diese untereinander vermischt/zusammengeführt und sortiert und als Array wieder zurückgeliefert an das Template. Damit diese Funktion in den Template-Scripten zur Verfügung steht, muss diese ganz oben für Smarty als Plugin registriert werden.

 

$smarty->registerPlugin('function', 'allBoxesOrderedInTargetPositions', 'allBoxesOrderedInTargetPositions');

/**
 * Author: mediaDESIGN St.Kraft 2019-02-25
 *
 * @param string|array $mixed
 * @param string|null $key
 * @return null|string
 */
function hole_PositionBoxTitel($mixed, $key="cTitel") {
	$arrBoxTitel = Shop::DB()->query("SELECT t.kBox, t.cTitel FROM tboxen as t WHERE ePosition = '".$mixed['ePosition']."' AND cTitel != ''",2);
	$returnBoxTitel = array();
	foreach ($arrBoxTitel as $Rec) {
		if ($key == 'cTitel') {
			$returnBoxTitel[$Rec->cTitel] = $Rec->kBox;
		} else {
			$returnBoxTitel[$Rec->kBox] = $Rec->cTitel;
		}
	}
	return $returnBoxTitel;
}


/**
 * Author: mediaDESIGN St.Kraft 2019-02-25
 *
 * @param $params
 * @param $smarty
 */
function allBoxesOrderedInTargetPositions($params, &$smarty) {
	global $Einstellungen;
	$html = '';

	$arrBoxesOrdering = array();
	$arrPositionBoxes = array();

	$arrBoxesOrdering = array_map('trim', explode('|', $Einstellungen['template']['productboxpositions']['boxes_order_'.$params['position']]));
	foreach ($params['frontpageProdBoxes'] as $BxIdx => $bx) {
		if (isset($params['frontpageProdBoxes'][$BxIdx]->Artikel)) {
			# $params['frontpageProdBoxes'][$BxIdx]->Artikel = 'cleared';
		}
	}

	if (is_array($params['topBoxes']) && count($params['topBoxes'])) {
		$topBoxesTitel = hole_PositionBoxTitel(array('ePosition' => 'top'));
		# echo "<pre>".print_r($topBoxesTitel,1)."</pre>";
	}

	if (count($arrBoxesOrdering)) {
		foreach ($arrBoxesOrdering as $selectedBoxId) {
			$selectedBoxId = trim($selectedBoxId);

			if (is_array($params['topBoxes']) && count($params['topBoxes'])) {
				foreach ($params['topBoxes'] as $BxIdx => $topBox) {
					$oBox = new stdClass();

					if (isset($topBoxesTitel[$selectedBoxId])) { $selectedBoxId = $topBoxesTitel[$selectedBoxId]; }

					$compare = (is_numeric($selectedBoxId)) ? 'kBox' : 'cTitel';

					if ($topBox['obj']->$compare == $selectedBoxId) {
						if ($topBox['obj']->nContainer > 0) {
style="background:lightgrey;">' . $topBox['obj']->innerHTML . '</div>';
							$oBox->type 	= 'ContainerBox';
							$oBox->id   	= $topBox['obj']->kBox;
							$oBox->html 	= $topBox['obj']->innerHTML;
							$oBox->idx		= $BxIdx;
						} else {
style="background:lightgrey;">' . $topBox['obj']->cInhalt . '</div>';
							$oBox->type 	= 'CustomBox';
							$oBox->id   	= $topBox['obj']->kBox;
							$oBox->idx		= $BxIdx;
						}
						$arrResult[] = $oBox;
					}
				}
			}

			if (is_array($params['frontpageProdBoxes']) && count($params['frontpageProdBoxes'])) {
				foreach ($params['frontpageProdBoxes'] as $BxIdx => $fpProdBox) {
					if ($fpProdBox->name == $selectedBoxId) {
						$oBox = new stdClass();
						if (isset($fpProdBox->Artikel->elemente) && count($fpProdBox->Artikel->elemente)) {
							$oBox->type 	= 'fpProductSliderBox';
							$oBox->idx   	= $BxIdx;
							$oBox->name   	= $fpProdBox->name;
							$arrResult[] 	= $oBox;
						}
					}
				}
			}

		}
	}


	if (isset($params['assign'])) {
		$smarty->assign($params['assign'], $arrResult);
	}
}

 

Boxenanzeige oberhalb des Hauptseiteninhaltes über die layout/index.tpl

Damit durch das Template alle Boxen sortiert ausgegen werden können, erfolgt in meinen Templates projektabhängig die Einfügung in das Script layout/index.tpl unterhalb der Zeile {include file="snippets/extension.tpl"}.

{if isset($Einstellungen.template.productboxpositions.boxes_order_above)}
    {assign var='BoxesVorInhalt' value=$Einstellungen.template.productboxpositions.boxes_order_above}
    {load_boxes_raw type='top' assign='arrBoxesTop' array=true}
    {allBoxesOrderedInTargetPositions position='above' topBoxes=$arrBoxesTop frontpageProdBoxes=$StartseiteBoxen assign='orderedBoxesArr'}

    {foreach name=sortierteBoxen from=$orderedBoxesArr item=Bx}
        {if $Bx->type == 'ContainerBox' }
            <div class="{$Bx->type} above Container_{$Bx->id}">
                {$Bx->html}
            </div>
        {elseif $Bx->type == 'CustomBox'}
            <div class="{$Bx->type} above Container_{$Bx->id}">
                {include file=$arrBoxesTop[$Bx->idx]['tpl'] oBox=$arrBoxesTop[$Bx->idx]['obj']}
            </div>
        {elseif $Bx->type == 'fpProductSliderBox'}
            {assign var='Box' value=$StartseiteBoxen[$Bx->idx]}
            {if isset($Box->cURL)}
                {assign var='moreLink' value=null}
                {assign var='moreTitle' value=null}
                {if $Box->name === 'TopAngebot'}
                    {lang key="topOffer" section="global" assign='title'}
                    {lang key='showAllTopOffers' section='global' assign='moreTitle'}
                {elseif $Box->name === 'Sonderangebote'}
                    {lang key="specialOffer" section="global" assign='title'}
                    {lang key='showAllSpecialOffers' section='global' assign='moreTitle'}
                {elseif $Box->name === 'NeuImSortiment'}
                    {lang key="newProducts" section="global" assign='title'}
                    {lang key='showAllNewProducts' section='global' assign='moreTitle'}
                {elseif $Box->name === 'Bestseller'}
                    {lang key="bestsellers" section="global" assign='title'}
                    {lang key='showAllBestsellers' section='global' assign='moreTitle'}
                {/if}
                {assign var='moreLink' value=$Box->cURL}
                <div class="{$Bx->type} above {$Bx->name}" style="background:#ffffcc">
                {include file='snippets/product_slider.tpl' productlist=$Box->Artikel->elemente title=$title hideOverlays=true moreLink=$moreLink moreTitle=$moreTitle}
                </div>
            {/if}
        {/if}
    {/foreach}
{/if}

Interessant sind u.a. folgende 3 Zeilen:

{assign var='BoxesVorInhalt' value=$Einstellungen.template.productboxpositions.boxes_order_above}

Hiermit lesen wir aus unserer Shop-Template-Konfiguration die Liste der Boxen aus. Dabei verwenden wir unsere Parameter-Bezeichnungen aus den XML-Settings. In diesem Fall interessieren uns die Boxen oberhalb des Seitenhauptinhaltes, also above und legen diese in die Smarty-Variable BoxesVorInhalt.

Spätere Anmerkung:
Natürlich kann man die Parameter aus der Templ.konfig. auch hier auslesen und dann an die functions.php übergeben. Da diese aber nur in der functions.php und nicht im Template-Script benötigt wird, wurde das Holen in die functions.php verschoben, so wie oben zu sehen. Insoforn ist diese Zeile obsolet.

 

{load_boxes_raw type='top' assign='arrBoxesTop' array=true}

Mit dieser Zeile holen wir uns mittels der EVO-Parent-Funktion load_boxes_raw() aus der functions.php alle Boxen die für die Position top bestimmt sind. top entspricht in der Boxenverwaltung dem Bereich Header. Nicht verwirren lassen: JTL-Shop/EVO kennt nur diese Position Header eigentlich für Boxen oberhalb des Contents. Wir nutzen Header-Position aber auch unterhalb des Contents. Weiter unten ist dann also below für unterhalb des Contents ebenfalls in Position top für den Header anzulegen :-). (Anmerkung: Im Footer-Bereich werden als Postition bottom nur die Boxen verwaltet, die wirklich ganz unten im Layout im Footer erscheinen sollen.)

{allBoxesOrderedInTargetPositions position='above' topBoxes=$arrBoxesTop frontpageProdBoxes=$StartseiteBoxen assign='orderedBoxesArr'}

Hier nun verwenden wir unsere neue eigene Methode allBoxesOrderedInTargetPositions(), die wir über unsere Child-functions.php hinzugefügt hatten. Ihr übergeben wir die schon vorhandenen Objekte für die Startseiten-Produkt-Slider-Boxen und die Custom-Boxen der Postion top wie oben geholt über Zeile 2.

 

Boxenanzeige unterhalb des Hauptseiteninhaltes über die page/index.tpl

Nun wiederholen wir diese Ausgabe für alle Boxen, die wir unterhalb des Hauptseiteninhalts anzeigen wollen. Dazu wird obige Scriptsequenz fast identisch jedoch in das Script page/index.tpl z.B. unterhalb der Zeile {include file="productwizard/index.tpl"} eingefügt. Lediglich alle above-Fragmente, Parameter und Variablenbezeichnungen müssen ersetzt werden durch below.

 

{if isset($Einstellungen.template.productboxpositions.boxes_order_below)}
    {* assign var='BoxesNachInhalt' value=$Einstellungen.template.productboxpositions.boxes_order_below *}
    {load_boxes_raw type='top' assign='arrBoxesTop' array=true}
    {allBoxesOrderedInTargetPositions position='below' topBoxes=$arrBoxesTop frontpageProdBoxes=$StartseiteBoxen assign='orderedBoxesArr'}

    {foreach name=sortierteBoxen from=$orderedBoxesArr item=Bx}
        {if $Bx->type == 'ContainerBox' }
            <div class="{$Bx->type} above Container_{$Bx->id}">
                {$Bx->html}
            </div>
        {elseif $Bx->type == 'CustomBox'}
            <div class="{$Bx->type} above Container_{$Bx->id}">
                {include file=$arrBoxesTop[$Bx->idx]['tpl'] oBox=$arrBoxesTop[$Bx->idx]['obj']}
            </div>
        {elseif $Bx->type == 'fpProductSliderBox'}
            {assign var='Box' value=$StartseiteBoxen[$Bx->idx]}
            {if isset($Box->cURL)}
                {assign var='moreLink' value=null}
                {assign var='moreTitle' value=null}
                {if $Box->name === 'TopAngebot'}
                    {lang key="topOffer" section="global" assign='title'}
                    {lang key='showAllTopOffers' section='global' assign='moreTitle'}
                {elseif $Box->name === 'Sonderangebote'}
                    {lang key="specialOffer" section="global" assign='title'}
                    {lang key='showAllSpecialOffers' section='global' assign='moreTitle'}
                {elseif $Box->name === 'NeuImSortiment'}
                    {lang key="newProducts" section="global" assign='title'}
                    {lang key='showAllNewProducts' section='global' assign='moreTitle'}
                {elseif $Box->name === 'Bestseller'}
                    {lang key="bestsellers" section="global" assign='title'}
                    {lang key='showAllBestsellers' section='global' assign='moreTitle'}
                {/if}
                {assign var='moreLink' value=$Box->cURL}
                <div class="{$Bx->type} below {$Bx->name}" style="background:#ffffcc">
                    {include file='snippets/product_slider.tpl' productlist=$Box->Artikel->elemente title=$title hideOverlays=true moreLink=$moreLink moreTitle=$moreTitle}
                </div>
            {/if}
        {/if}
    {/foreach}
{/if}

 

Das wars. Ich hoffe die Anleitung war verständlich und nachvollziehbar und ich habe nichts vergessen. Wenn Sie Unterstützung bei der Implementierung benötigen, können Sie mich gern kontaktieren.