Wenn man ein und denselben Artikel in mehreren Kategorien einsortiert hat, klappt sich die Kategorien-Navigation von xt:Commerce in der Produkt-Detail-Ansicht oft ganz unerwartet und ganz woanders aus, als man “hergekommen” ist.

Anders ausgerdückt: Ein Artikel ist in drei Kategorien drin - Man klickt ihn an - Aber welcher “Pfad” ist der “richtige”? Aktuell wird immer der Pfad genommen, der mit der niedrigsten Kategorien-ID beginnt, in dem der gewählte Artikel enthalten ist.

Auf welchem Weg der Besucher zu diesem Artikel gefunden hat, wird jedoch nicht berücksichtigt. So klappt sich die Kategorien-Navigation bei “mehrfach verlinkten” Produkten gerne einfach “irgendwo” aus.

Je nach Shop passen ja einige Artikel in mehrere Kategorien - Und damit die Navi nicht weiterhin wild “hin- und herspringt” - Hier ein paar Lösungsvorschläge … :

 

 

Erste Variante: $_GET[’cPath’]

Das teilweise überraschende und oftmals ungewollte Verhalten lässt sich recht einfach vermeiden, indem man in Artikel-Listen die Links so bildet, dass global $cPath mitgeschleift wird.

 

Schritt 1:

Man greift sich die Datei “inc/xtc_product_link.inc.php” und erweitert sie um einen Parameter $cPath - für den sollte man gleich false als Standardwert festlegen, damit’s an anderen Stellen keine Schwierigkeiten gibt.

Im Original sieht’s so aus …

function xtc_product_link($pID, $name='') {

	$pName = xtc_cleanName($name);
	$link = 'info=p'.$pID.'_'.$pName.'.html';
	return $link;
}

… und nach der Bearbeitung so:

function xtc_product_link($pID, $name='', $cPath = false) {

	$pName = xtc_cleanName($name);
	if($cPath) {
		$cPath = 'cPath='.$cPath.'&';
	}
	$link = $cPath.'info=p'.$pID.'_'.$pName.'.html';
	return $link;
}

 

Schritt 2:

Als nächstes bearbeitet man “includes/classes/product.php” und sucht dort die Funktion “buildDataArray”. Diese braucht $cPath in den globalen Variablen.

Die zweite Zeile …

function buildDataArray(&$array,$image='thumbnail') {
	global $xtPrice,$main;

… muss also ergänzt werden:

function buildDataArray(&$array,$image='thumbnail') {
	global $xtPrice,$main,$cPath;

Etwas weiter unten wird das Rückgabe-Array zusammengesetzt, aus dem xt:Commerce (meistens) die Smarty-Tags bildet:


return array ('PRODUCTS_NAME' => $array['products_name'],
	'COUNT'=>$array['ID'],
	'PRODUCTS_ID'=>$array['products_id'],
	'PRODUCTS_VPE' => $this->getVPEtext($array, $products_price['plain']),
	'PRODUCTS_IMAGE' => $this->productImage($array['products_image'], $image),
	'PRODUCTS_LINK' => xtc_href_link(FILENAME_PRODUCT_INFO, xtc_product_link($array['products_id'], $array['products_name'])),
	'PRODUCTS_PRICE' => $products_price['formated'],
	'PRODUCTS_TAX_INFO' => $main->getTaxInfo($tax_rate),
	'PRODUCTS_SHIPPING_LINK' => $main->getShippingLink(),
	'PRODUCTS_BUTTON_BUY_NOW' => $buy_now,
	'PRODUCTS_SHIPPING_NAME'=>$shipping_status_name,
	'PRODUCTS_SHIPPING_IMAGE'=>$shipping_status_image,
	'PRODUCTS_DESCRIPTION' => $array['products_description'],
	'PRODUCTS_EXPIRES' => $array['expires_date'],
	'PRODUCTS_CATEGORY_URL'=>$array['cat_url'],
	'PRODUCTS_SHORT_DESCRIPTION' => $array['products_short_description'],
	'PRODUCTS_FSK18' => $array['products_fsk18']);
	

Dort ergänzt man einen weiteren Wert, quasi den “normalen” Artikel-Link plus $cPath-Parameter, damit hat man in Produkt-Listen nachher zwei “Link-Typen” zur Auswahl, die man im Template benutzen kann.


'PRODUCTS_PATH_LINK' => xtc_href_link(FILENAME_PRODUCT_INFO, xtc_product_link($array['products_id'], $array['products_name'], $cPath)),
	

Im Zusammenhang sieht’s so aus:


return array ('PRODUCTS_NAME' => $array['products_name'],
	'COUNT'=>$array['ID'],
	'PRODUCTS_ID'=>$array['products_id'],
	'PRODUCTS_VPE' => $this->getVPEtext($array, $products_price['plain']),
	'PRODUCTS_IMAGE' => $this->productImage($array['products_image'], $image),
	'PRODUCTS_LINK' => xtc_href_link(FILENAME_PRODUCT_INFO, xtc_product_link($array['products_id'], $array['products_name'])),
	// NEU!
	'PRODUCTS_PATH_LINK' => xtc_href_link(FILENAME_PRODUCT_INFO, xtc_product_link($array['products_id'], $array['products_name'], $cPath)),
	// -->
	'PRODUCTS_PRICE' => $products_price['formated'],
	'PRODUCTS_TAX_INFO' => $main->getTaxInfo($tax_rate),
	'PRODUCTS_SHIPPING_LINK' => $main->getShippingLink(),
	'PRODUCTS_BUTTON_BUY_NOW' => $buy_now,
	'PRODUCTS_SHIPPING_NAME'=>$shipping_status_name,
	'PRODUCTS_SHIPPING_IMAGE'=>$shipping_status_image,
	'PRODUCTS_DESCRIPTION' => $array['products_description'],
	'PRODUCTS_EXPIRES' => $array['expires_date'],
	'PRODUCTS_CATEGORY_URL'=>$array['cat_url'],
	'PRODUCTS_SHORT_DESCRIPTION' => $array['products_short_description'],
	'PRODUCTS_FSK18' => $array['products_fsk18']);
	

 

Schritt 3:

Im Template müssen schließlich im Ordner “module/product_listing” in den .html-Dateien nur noch alle {$module_data.PRODUCTS_LINK} durch {$module_data.PRODUCTS_PATH_LINK} ersetzt werden.

 

Hinweise:

Diese Lösung ist natürlich noch nicht optimal - Denn auf die Weise werden verschiedene Links zum gleichen Artikel “publiziert”, Suchmaschinen haben so etwas nicht sonderlich gerne. Außerdem funktioniert’s mit den meisten Modulen für Suchmaschinen freundliche URLs nicht, die ja eindeutige Links produzieren.

Ein anderer Ansatz ist in Arbeit: Es wird wohl auf eine Bearbeitung der “application_top.php” hinauslaufen, und zwar so, dass in der $_SESSION ID und Pfad der letztbesuchten Kategorie abgelegt werden und halt “ganz am Anfang” dann global $cPath entsprechend gebildet wird. Dann müssen noch ein paar Fehler abgefangen werden, die z.B. auftauchen würden, wenn man aus einer Produktliste direkt auf einen Artikel klickt, der gar nicht in der aktuellen Kategorie ist (beispielsweise aus der Bestsellers-Liste oder Ähnliches).

In Kürze mehr dazu.

 

 

Zweite Variante mit $_SESSION

Vorweg: Bin selbst noch am Testen, aber es scheint ganz gut zu klappen. Wer’s mittesten möchte - Die oben beschriebenen Änderungen müssen nicht allesamt über den Haufen geworfen werden, also keine Sorge.

Es reicht absolut aus, im Template in den Produkt-Listen einfach wieder {$module_data.PRODUCS_LINK} zu benutzen, dann läuft alles so wie gehabt.

Die nun folgenden Änderungen spielen sich in der “includes/application_top.php” ab (von dieser Datei also eine Sicherheitskopie anfertigen) - Und weil die “application_top.php” eine ziemlich zentrale Systemdatei ist, bitte nicht gleich am Live-Shop ausprobieren.

Eine zusätzliche Datei müssen wir ebenfalls anlegen. Also los!

 

Schritt 1:

Die Grundidee ist die, den bereits in der Kategorien-Ansicht gebildeten $cPath nun in der Session mitzuschleifen und (wenn vorhanden) eben diesen als “Produkt-Pfad” einzusetzen.

Ohne zusätzliche Kontrolle kann (und wird) dabei natürlich dauernd was daneben gehen - Man ist in der Kategorie “Goldhamsterfutter”, sieht in den Bestsellers günstiges Katzenstreu - klickt darauf - Und das Menü öffnet sich bei “Kleintierbedarf”. Ob das Katzenstreu nun im “Kleintierbedarf” drin ist oder nicht.

Daher brauchen wir eine Funktion, die prüft, ob der Artikel mit der ID 123 auch wirklich in der Kategorie 456 drin ist, damit am Ende keine “unmöglichen” Pfade den kaufwilligen Besucher verwirren.

Also erzeugen wir eine Datei “xtc_is_product_in_cat.inc.php” mit folgendem Inhalt …


<?php

function xtc_is_product_in_cat($pID = false, $cID = false) {

	if($pID && $cID) {

		// Kundengruppen-Check
		if (GROUP_CHECK=='true') {
			$group_check = "and c.group_permission_".$_SESSION['customers_status']['customers_status_id']." = 1 ";
		}

		// DB-Abfrage
		$dbQuery = xtc_db_query("
			select 	c.categories_id
			from	".TABLE_PRODUCTS_TO_CATEGORIES." p2c,
				".TABLE_CATEGORIES." c
			where	p2c.products_id = ".intval($pID)."
			and	p2c.categories_id = ".intval($cID)."
			and	p2c.categories_id = c.categories_id
			and	c.categories_status = 1
			".$group_check."
		");

		$dbQuery = xtc_db_fetch_array($dbQuery,true);

		if($dbQuery['categories_id']) {
			return true;
		}

	}
	return false;

}

?>
	

… und laden sie in den Ordner “inc” hoch.

 

Schritt 2:

Danach bearbeiten wir die Datei “includes/application_top.php” - Wobei als erstes unsere neue Funktion eingebunden werden muss.

Ab etwa Zeile 88 stehen dort lauter require-Anweisungen. Dort bringt man die neue Datei ebenfalls unter …

require_once (DIR_FS_INC.'xtc_is_product_in_cat.inc.php');

… ist klar: Am besten ziemlich am Ende des ganzen Blocks (ca. Zeile 160) - denn wenn die zu früh eingebunden wird, gibt’s Fehlermeldungen, weil z.B. die Funktion xtc_db_query noch nicht bekannt ist. So kann jedenfalls schon mal nix passieren.

 

Schritt 3:

Jetzt müssen wir ein bisschen scrollen. Nicht verwirren lassen. Denn es gibt auch vorher schon ein paar Stellen, an denen “irgendwas mit cPath” auftaucht.

Aber ab Zeile 450 wird’s interessant - denn erst dort wird der cPath “endgültig festgelegt”, und zwar auch im Falle von “wir sind auf einer Produkt-Detailseite”. Im Original sieht dieser Bereich so aus:


// calculate category path
if (isset ($_GET['cPath'])) {
	$cPath = xtc_input_validation($_GET['cPath'], 'cPath', '');
}
elseif (is_object($product) && !isset ($_GET['manufacturers_id'])) {
	if ($product->isProduct()) {
		$cPath = xtc_get_product_path($actual_products_id);
	} else {
		$cPath = '';
	}
} else {
	$cPath = '';
}
	

Das alles auskommentieren und hiermit ersetzen:


// calculate category path
if (isset ($_GET['cPath'])) {
	$cPath = xtc_input_validation($_GET['cPath'], 'cPath', '');
	$_SESSION['CatPath'] = $cPath;
	$_SESSION['CatID'] = array_pop(explode('_',$cPath));
}
elseif (is_object($product) && !isset ($_GET['manufacturers_id'])) {
	if ($product->isProduct()) {
		if ($_SESSION['CatPath'] && $_SESSION['CatID'] && xtc_is_product_in_cat($actual_products_id,$_SESSION['CatID'])) {
			$cPath = $_SESSION['CatPath'];
		} else {
			$cPath = xtc_get_product_path($actual_products_id);
			unset($_SESSION['CatPath']);
			unset($_SESSION['CatID']);
		}
	} else {
		$cPath = '';
	}
} else {
	$cPath = '';
}
	

Die ersten beiden hinzugefügten Zeilen sind lediglich dafür da, die zwei neuen Session-Variablen mit dem Pfad und der ID der aktuellen Kategorie zu füllen.

Danach geht’s darum, im Falle einer Produkt-Detail-Ansicht zu überprüfen, ob dieses Produkt im “Session-Pfad” enthalten ist.

Wenn ja, dann bekommt $cPath den Wert von $_SESSION['CatPath'] - Wenn nein, dann wird $cPath wie gehabt aus der aktuellen Produkt-ID gebildet. Wichtig dabei ist auch, die neuen Session-Variablen in diesem Fall wieder zurückzusetzen, damit nicht beim übernächsten Klick irgendetwas “merkwürdiges” passiert.

 

(Optional) Schritt 4:

Wenn man zwischendrin aufs Impressum oder auf die Sonderangebote klickt, wird $current_category_id gleich Null. Also in allen Fällen, in denen keine Kategorie festgestellt werden kann.


if (xtc_not_null($cPath)) {
	$cPath_array = xtc_parse_category_path($cPath);
	$cPath = implode('_', $cPath_array);
	$current_category_id = $cPath_array[(sizeof($cPath_array) - 1)];
} else {
	$current_category_id = 0;
}

Ist vielleicht Geschmackssache, ob man in dem Fall auch die beiden Session-Variablen zurücksetzt.

Tut man’s nicht, kann man auf die Sonderangebote klicken, und die Kategorien-Navi macht sich (wenn man vorher in der passenden Kategorie war und man einen passenden Artikel gewählt hat) an der gleichen Stelle wieder auf. Kann auch ein Vorteil sein …

Wie dem auch sei - der Vollständigkeit halber, und weil’s zum Testen ist:


if (xtc_not_null($cPath)) {
	$cPath_array = xtc_parse_category_path($cPath);
	$cPath = implode('_', $cPath_array);
	$current_category_id = $cPath_array[(sizeof($cPath_array) - 1)];
} else {
	$current_category_id = 0;
	unset($_SESSION['CatPath']);
	unset($_SESSION['CatID']);
}

Ohne diese Änderung werden $_SESSION['CatPath'] und $_SESSION['CatID'] mit jedem Klick auf eine andere Kategorie neu gefüllt. Aber NUR geleert, wenn man einen Artikel “erwischt”, bei dem der Pfad nicht stimmen kann.

Mit dieser Änderung leeren sich $_SESSION['CatPath'] und $_SESSION['CatID'] bei JEDEM Klick auf einen Shop-Bereich, der kein Artikel oder keine Kategorie ist. Die Informationen aus den zuvor besuchten Kategorien gehen damit dann verloren.

 

 

Nachtrag

Folgende etwas schlankere Variante funktioniert auch - und hat zudem den Vorteil, dass die Funktion xtc_is_product_in_cat (immerhin eine Datenbank-Abfrage) nur dann benutzt wird, wenn $_SESSION['CatPath'] tatsächlich gefüllt ist:


// calculate category path
if (isset ($_GET['cPath'])) {
	$cPath = xtc_input_validation($_GET['cPath'], 'cPath', '');
	$_SESSION['CatPath'] = $cPath;
}
elseif (is_object($product) && !isset ($_GET['manufacturers_id'])) {
	if ($product->isProduct()) {
		if ($_SESSION['CatPath']){
			if (xtc_is_product_in_cat($actual_products_id,array_pop(explode('_',$_SESSION['CatPath'])))) {
				$cPath = $_SESSION['CatPath'];
			} else {
				$cPath = xtc_get_product_path($actual_products_id);
				unset($_SESSION['CatPath']);
			}
		} else {
			$cPath = xtc_get_product_path($actual_products_id);
		}
	} else {
		$cPath = '';
	}
} else {
	$cPath = '';
}
	

Weiter unten braucht man entsprechend nur noch einen Session-Eintrag zurückzusetzen, so man es denn möchte.

Ich persönlich find’s schöner, das nicht zu tun.

Denn wenn man beispielsweise direkt aus einer Produkt-Detail-Ansicht heraus die Suche benutzt und dann einen Treffer anwählt, klappt sich die Navigation wieder dort aus, wo sie vorher ausgeklappt war - sofern der neue Artikel auch in der vorher besuchten Kategorie enthalten ist.

 

 

Nachtrag2 - Für Gambio

Daniel Schnadt hat mir freundlicherweise eine Partner-Version von » Gambio 2008 zur Verfügung gestellt. Mit Gambio funktioniert’s ebenfalls - beim ersten Versuch wurden aber oftmals zwei Kategorien als aktiv markiert.

An welcher Stelle und warum genau immer eine zusätzliche Zahl an $cPath “gehängt” wird, habe ich nicht herausgefunden.

Das lässt sich jedoch wie folgt umgehen: Die Datei “gm/classes/GMCat.php” öffnen und nach der Funktion GMCat($cPath) suchen (etwa Zeile 39). Dort ganz zu Anfang unseren Session-Eintrag einfügen:


function GMCat($cPath) {     

	// Änderung
	if( (basename($_SERVER['SCRIPT_NAME']) == FILENAME_DEFAULT || basename($_SERVER['SCRIPT_NAME']) == FILENAME_PRODUCT_INFO) && isset($_SESSION['CatPath']) ) {
		$cPath = $_SESSION['CatPath'];
	}
	// Änderung Ende -->     

	$this->gmSEOBoost	= new GMSEOBoost();
	$this->cPath	= $cPath;
	$this->icon_cat_use = gm_get_conf('GM_LOGO_CAT_USE');
	$this->icon_w	= gm_get_conf('GM_LOGO_CAT_SIZE_W');
	$this->icon_h	= gm_get_conf('GM_LOGO_CAT_SIZE_H');

	return;
}
	

Diese Änderung ist übrigens “ungefährlich”, denn $cPath wird nur dann beeinflusst, wenn es einen “CatPath”-Session-Eintrag gibt.

Die beiden Fragen nach basename($_SERVER['SCRIPT_NAME']) braucht man, damit die Kategorien-Navi nicht dauerhaft an der Stelle ausgeklappt bleibt, an der man “vorhin gewesen” ist (beispielsweise, wenn man von Kategorie zum Impressum klickt).

Ansonsten kann (jedenfalls bei Gambio 2008) genauso vorgegangen werden wie bei xt:Commerce auch. Siehe “Zweite Variante mit $_SESSION” bzw. “Nachtrag” - Und das funktioniert sowohl ohne Suchmaschinen freundliche URLs, als auch mit den “Standard” Suchmaschinen freundlichen URLs und Gambio SEO-Boost.

Doppelten Content erzeugt man so nicht, die URLs der einzelnen Artikel bleiben immer dieselben. Lediglich die Navigation klappt sich auf die Weise eben dort aus, wo man es “erwarten würde” - und auch die Anzeige in der BreadCrumb stimmt mit dem “Klick-Weg” überein.

 

Ich wünsche viel Spaß beim Basteln!
Und wie immer: Vorsichtshalber bitte nicht gleich am Live-Shop ausprobieren!

 

 

Nachtrag 3: Achtung! Internet Explorer 8
(10. Juni 2009)

Warum mir der Browser sonst noch so auf den Senkel geht - dazu vermutlich bald an anderer Stelle mehr. Viele fiese Kleinigkeiten, von denen die schlimmste ist, dass man das Teil einfach nicht mehr deinstallieren kann …

Es kann sein, dass dieser “Trick” im IE8 einfach nicht funktioniert. Nach viel Herumprobiererei bei unterschiedlichen Hostern und mit verschiedenen Templates bin ich endlich auf die Idee gekommen, dass einige JavaScripts Probleme machen können. Dazu gehören u.a. “HighSlide” und “jQuery”.

Witzigerweise machen diese Tools keine Schwierigkeiten, sobald immer Session-IDs an die Links angehängt werden. Oder aber, wenn Suchmaschinen freundliche URLs ausgeschaltet sind.

Ein ziemlich kurioser Bug, bei dem wohl mehrere Faktoren (u.a. auch Server-Einstellungen) vermutlich ihren Einfluss haben. Denn es gibt auch Shops mit HighSlide UND dieser Modifikation und OHNE angehängte Session-IDs - da läuft’s im IE8 problemlos. Beispiel: Urbantrendsetter - Während anderswo bei HighSlide gleich wieder alles vorbei ist.

Und bitte verlassen Sie sich nicht “blind” auf Tools wie “IE-Tester” - Mit dem funktioniert Etliches, das den “echten” IE8 ins Schwitzen bringt.

Also am besten testen. Und wenn’s tatsächlich nicht klappt, kann man Folgendes ausprobieren: Man greift sich die Datei “inc/xtc_href_link.inc.php” und sucht hiernach …

// remove session if useragent is a known Spider
if ($truncate_session_id) $sid=NULL;

… und davor schreibt man …


if(stristr($_SERVER['HTTP_USER_AGENT'], 'MSIE 8') && SEARCH_ENGINE_FRIENDLY_URLS == 'true'){
	$sid = session_name() . '=' . session_id();
}
	

… dann sollte die Sache wieder laufen. Getestet mit xt:Commerce 3.04 SP2.1 - Mit den standard Suchmaschinen freundlichen URLs und mit ShopStat.