Shopware - formatierte Beschreibungstexte im Listing anzeigen

Formatierte Artikelbeschreibung in Artikelübersicht

Formatierte Artikelbeschreibung in Artikelübersicht

Beim Anlegen einer Artikelbeschreibung im Shopware-Backend definiert man, mit Hilfe des TinyMCE-Editors, sowohl den Inhalt als auch das Layout des Textes. Der TinyMCE-Editior reichert den Text mit HTML- und CSS-Anweisungen an. Dadurch erscheint die Beschreibung im Frontend mit der gewünschten Formatierung.

Doch nicht an allen Stellen in Frontend wird die Formatierung mitgeladen. In den Produktboxen der Artikelübersichtsseite erscheint die Beschreibung unformatier(siehe Abbildung oben rechts).

Der Grund dafür ist leicht nachvollziehbar – auch wenn der Text aus nur wenig Zeichen besteht kann er durch Zeilenumbrüche, Schriftgrößen usw. schnell zu groß werden. Wenn Sie also die Formatierung in der Artikelübersicht zulassen, müssen Sie die Verantwortung dafür übernehmen, dass die Beschreibung angemessen dargestellt wird.

Wie wird die Formatierung verhindert?

Um zu verstehen, warum eigentlich ein, als HTML definierter Text ohne Formatierung im Browser erscheint schauen wir uns zunächst den entsprechenden Smarty-Block:

  {* Product description *}
  {block name='frontend_listing_box_article_description'}
  <div class="product--description">
    {$sArticle.description_long|strip_tags|truncate:380}
  </div>
  {/block}
  

Wir sehen hier also, dass der Wert der Beschreibung durch den Modifikator „strip_tags“ gejagt und dann auf 380 Zeichen gekürzt wird. „strip_tags“ sorg dafür, dass alles zwischen der „<“ und „>“, inklusive der Klammern herausgeschnitten wird.

Smarty ist sehr schnell und leicht anzupassen. Wir überschreiben also den Block „Frontend_listing_box_article_description“ und entfernen den Modifikator „strip_tags“. Überraschenderweise hat sich dadurch die Darstellung in der Artikelübersicht kein Bisschen geändert. Die Formatierungen fehlen weiterhin.

Also müssen wir etwas tiefer graben. Wir schauen uns an, wie die Daten geladen werden. Dazu öffnen wir die Action „index“ des Controllers „Listing“ an. In der Zeile 194 sehen wir, dass die Daten durch das Modul „sArticles“, in der Methode „sGetArticlesByCategory“ geladen werden. Schaut man sich den Ablauf genauer an stößt man auf diese Codezeile:

$article['description_long'] = 
  $this->sOptimizeText($article['description_long']);
  

Die Methode sOptimizeText „optimiert“ also unsere Formatierung weg. Shopware hat sich hier also doppelt abgesichert – einmal beim Laden und einmal bei der Darstellung der Daten.

Was ist die Lösung?

Wir haben gesehen, dass wir mit einer reinen Smarty-Lösung nicht sehr weit kommen. Also müssen wir uns einen Plugin schreiben. Hier ist die Idee die wir hier umsetzen wollen:

  1. Wir melden uns an dem Controller „Listing“ an und fangen die Action „index“ ab
  2. In unserem Handler holen wir uns die, bereits geladenen Artikeln in der Variable „sArticles“
  3. Wir laden die formatierten Artikelbeschreibung aus der Datenbank und ersetzen damit die, in der sArticles-Variable.

Handler am Controller „Listing“ registrieren

In der „install“-Methode unseres Plugins fügen wir folgende Anweisung hinzu:

		$this->subscribeEvent(
			'Enlight_Controller_Action_PostDispatch_Frontend_Listing',
			'onFrontendPostDispatch'
		);

Händler schreiben

Wir wollen, dass unsere Anpassung sich nur auf die Action „index“ auswirkt. In allen anderen Fällen machen wir nichts:

		$controller = $arguments->getSubject();
		$view = $controller->View();
		$request = $controller->Request();
		
		if(!($request->getModuleName() == "frontend" && 
		     $request->getControllerName() =="listing" && 
		     $request->getActionName() == "index")){
			return;
		}

Als nächstes laden wir die „sArticles“-Variable und holen uns alle IDs der geladenen Artikel. Mit dem IDs werden wir später die formatierte Beschreibung laden.

		$articles = $view->getAssign('sArticles');
		$articleIds = $this->getArticleIds($articles);

Die Methode „getArticleIds“ sieht so aus:

	private function getArticleIds($articles){
		$ids = array();
		foreach ($articles as $art) {
			array_push($ids, $art['articleID']);
		}
		return $ids;
	}

Als nächstes prüfen wir ob die ID-Liste nicht leer ist (es kann sein, dass die Kategorie keine Artikel enthält) und laden die formatierten Beschreibungen:

		if(sizeof($articleIds) > 0) {
			$descriptions = $this->getDescriptionsArray($articleIds);

Aus den gesammelten Artikel-IDs machen wir In der Methode “getDescriptionsArray” eine „IN Klausel“:

	private function getDescriptionsArray($ids){
		$descriptions = array();
		$sql="SELECT id,description_long FROM s_articles where id in (?) ";

		$query  = str_replace('?', 
			substr(str_repeat('?, ', count($ids)), 0, -2), $sql);
		$results = Shopware()->Db()->fetchAll($query, $ids);
		
		foreach ($results as $result) {
			$descriptions[$result['id']] = $result['description_long'];
		}
		return $descriptions;
	}

Den zurückgegebenen „Descriptions“-Array verwenden wir als eine Map mit Artikel-ID als Key und Beschreibung als Value und ersetzen damit die unformatierten Beschreibungen im „sArticles“:

		foreach ($articles as &$art) {
			$art["description_long"] = $descriptions[$art['articleID']];
		}
		$view->assign('sArticles', $articles);

Damit hätten wie die PHP-seitige Limitierung behandelt. Natürlich müssen wir noch den Smarty-Block entschärfen. Dazu legen wir uns folgende Struktur an und registrieren diese in der View:

		$view->addTemplateDir($this->Path() . 'Views/');
		$view->extendsTemplate('frontend/listing/product-box/producBox_extension.tpl');

Im Template “productBox_extension.tpl” überschreiben wir den Block, wie folgt:

{block name='frontend_listing_box_article_description'}
    <div class="product--description">
        {$sArticle.description_long|truncate:100}
    </div>
{/block}

Anmerkung: durch die Formatierung wird der Text tendenziell größer. Deshalb wurde der Text zusätzlich auf 100 Zeichen beschränkt.

Nachladen per AJAX

Eine wichtige Sache fehlt noch. Beim Auflisten von Artikeln einer Kategorie lädt Shopware nur eine gewisse Anzahl von Artikeln. Erst beim Herunterscrollen werden restliche Artikeln nachgeladen. Dieses Nachladen müssen wir auch abfangen und auch dort die Beschreibung ersetzen. Glücklicherweise läuft es über dieselben Templates, wie auch beim initialen Laden. Wir müssen uns nur bei einem zusätzlichen Controller registrieren und den Action-Filter in Handler um eine weitere Action erweitern. Hier also der vollständige Code:

	public function install() {
		$this->subscribeEvent(
			'Enlight_Controller_Action_PostDispatch_Frontend_Listing',
			'onFrontendPostDispatch'
		);

		$this->subscribeEvent (
			'Enlight_Controller_Action_PostDispatch_Widgets_Listing',
			'onFrontendPostDispatch');
		
		return true;
	}

Der Handler:

	public function onFrontendPostDispatch(Enlight_Event_EventArgs $arguments)
	{
		$controller = $arguments->getSubject();
		$view = $controller->View();
		$request = $controller->Request();
		
		if(!($request->getModuleName() == "frontend" && 
		     $request->getControllerName() =="listing" && 
		     $request->getActionName() == "index") &&
		   !($request->getModuleName() == "widgets" &&
		     $request->getControllerName() =="Listing" && 
		     $request->getActionName() == "ajaxListing")) {
			return;
		}
		
		$articles = $view->getAssign('sArticles');
		$articleIds = $this->getArticleIds($articles);
		
		if(sizeof($articleIds) > 0) {
			$descriptions = $this->getDescriptionsArray($articleIds);
			
			foreach ($articles as &$art) {
				$art["description_long"] = 
					$descriptions[$art['articleID']];
			}
			
			$view->addTemplateDir($this->Path() . 'Views/');
			$view->extendsTemplate(
			  'frontend/listing/product-box/producBox_extension.tpl');
                   $view->assign('sArticles', $articles);
		}
	}

Und die Hilfsmethoden:

	private function getArticleIds($articles){
		$ids = array();
		foreach ($articles as $art) {
			array_push($ids, $art['articleID']);
		}
		return $ids;
	}
	
	private function getDescriptionsArray($ids){
		$descriptions = array();
		$sql="SELECT id,description_long FROM s_articles where id in (?) ";

		$query  = str_replace('?', 
			substr(str_repeat('?, ', count($ids)), 0, -2), $sql);
		$results = Shopware()->Db()->fetchAll($query, $ids);
		
		foreach ($results as $result) {
			$descriptions[$result['id']] = $result['description_long'];
		}
		
		return $descriptions;
	}