Ähnliche Produkte im OXID eShop verbessern

OXID eShop

In einem vergangenen Projekt sollten in einem OXID eShop ähnliche Produkte in der Detail-Ansicht gezeigt werden. Nach kurzer Suche stellte sich heraus, diese Funktionalität ist bereits vorhanden. Allerdings stellte sich genauso schnell heraus, dass die Ergebnisse oft Produkte enthalten, die sich garnicht ähnlich sind. Wirklich ähnliche Produkte im OXID eShop anzuzeigen ist also garnicht so einfach. Der Grund dafür ist, dass OXID in der Implementierung nur einen sehr rudimentären Vergleich auf Ähnlichkeiten macht. Um dies zu verbessern, habe ich die Funktion über ein Modul erweitert.

OK, OXID – finde ähnlichere Produkte

Die Prüfung der Ähnlichkeit von Produkten beschränkt sich bei der OXID eigenen Implementierung auf die Attribut-Namen. Das bedeutet, dass Produkte, die das Attribut „Farbe“ haben als ähnlich gelten. Ob die Farbe nun den Wert rot, gelb oder grün hat, wird dabei komplett ausser Acht gelassen. Unser Ziel ist es also zunächst einmal den Algorithmus so anzupassen, dass auch der Wert des Attributs in die Berechnung einfließt.

Ein weiteres Problem ist, dass der OXID Code nur Artikel berücksichtigt, die auch gesucht werden können. Damit fallen Attribute, die auf Varianten gesetzt sind komplett unter den Tisch. In den meisten Shop Setups mag das nicht problematisch sein. Bei mir war es das allerdings. Aus diesem Grund werden wir auch hier nochmal polieren.

Jetzt wird Code geschrieben

Für alle, die sich noch nicht lange mit dem OXID eShop beschäftigen ein Hinweis. Damit es später bei Systemupdates keine bösen Überraschungen gibt, machen wir niemals Änderungen am OXID Kern. Neue Funktionen, Funktionserweiterungen oder Funktionsersetzungen passieren immer über das Einbinden eines Moduls. Ich gehe hier davon aus, dass Du darüber Bescheid weißt. Falls nicht solltest Du einen Blick auf diese Tutorials riskieren.

Wir werden im folgenden drei Stellen in der Klasse oxarticles verändern. Dazu legen wir die Übliche Stuktur für ein Modul und unsere Erweiterungsklasse an. Diese Schritte überlasse ich dir. Wenn Du soweit bist, legen wir die folgenden Funktionen an:

  • protected function _getAttribsString(&$sAttribs, &$iCnt)
  • protected function _getSimList($sAttribs, $iCnt)
  • protected function _generateSimListSearchStr($sArticleTable, $aList)

Im folgenden möchte ich näher auf die Funktionen eingehen und diese mit leben füllen.

_getAttribsStrings

In dieser Funktion wird sonst nur der Attributname aus der Datenbank gelesen. Wir erweitern die Funktion nun so, dass auch die hinterlegten Werte zum Vergleich herangezogen werden:

protected function _getAttribsString(&$sAttribs, &$iCnt)
     {
         $oDB = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
         $sObj2AttView = getViewName('oxobject2attribute');            $sSelect =  "select oxattrid, oxvalue from {$sObj2AttView} where oxobjectid='".$this->getId()."' ".PHP_EOL;
         $sAttribs = '';
         $rs = $oDB->execute( $sSelect);
         $iCnt = 0;
         if ($rs != false && $rs->recordCount() > 0)
         {
             while (!$rs->EOF)
             {
                 if ($sAttribs)
                 {
                     $sAttribs .= ' or ';
                 }
 // HIER PRÜFEN WIR NICHT MEHR NUR AUF oxattrid SONDERN AUCH AUF oxvalue
                 $sAttribs .= '(oxattrid = '.$oDB->quote($rs->fields['OXATTRID']).' and oxvalue = '.$oDB->quote($rs->fields['OXVALUE']).')'.PHP_EOL;
                 $iCnt++;
                 $rs->moveNext();
             }
         }
     }

_getSimList

Hier wird aufgrund der Attribute eine Liste an Artikeln aufgebaut, die dem aktuellen Artikel ähnlich sind. Hier habe ich geändert, dass nicht die Varianten zurückgegeben werden, sondern deren Väter. Das verhindert, dass die Varianten aussortiert werden bzw – wenn die Varianten als Suchbar gesetzt sind – nicht nur Varianten von einem Artikel angezeigt werden.

    
protected function _getSimList($sAttribs, $iCnt) {
 $myConfig = $this->getConfig();
 $oDb      = oxDb::getDb( oxDb::FETCH_MODE_ASSOC );
 $sArticleTable = $this->getViewName();
 $sObj2AttView = getViewName('oxobject2attribute');


 // #523A
 $iAttrPercent = $myConfig->getConfigParam( 'iAttributesPercent' )/100;
 // 70% same attributes
 if ( !$iAttrPercent || $iAttrPercent < 0 || $iAttrPercent > 1) {
  $iAttrPercent = 0.70;
 }
 // #1137V iAttributesPercent = 100 doesnt work
 $iHitMin = ceil( $iCnt * $iAttrPercent );

 // we do not use lists here as we don't need this overhead right now
 $aList= array();
 $sSelect =  "select oxobjectid, oxart.oxparentid, count(*) as cnt from {$sObj2AttView} as t1 LEFT JOIN {$sArticleTable} as oxart on (oxobjectid = oxart.oxid) where
             ( $sAttribs )
             and t1.oxobjectid != ".$oDb->quote( $this->oxarticles__oxid->value )."
             group by t1.oxobjectid having count(*) >= $iHitMin ";

 $rs = $oDb->selectLimit( $sSelect, 50, 0 );
 $parents = array();
 if ($rs != false && $rs->recordCount() > 0) {
  while (!$rs->EOF) {
   $oxparentid = $rs->fields['OXPARENTID'];
   $oxobjectid = $rs->fields['OXOBJECTID'];

   if($oxparentid && $parents[$oxparentid] || !strcmp($oxobjectid,$this->oxarticles__oxid->value) || !strcmp($oxparentid,$this->oxarticles__oxid->value)) {
    $rs->moveNext();
    continue;
   }
   if($oxparentid){
    $parents[$oxparentid] = 1;
   } else {
    $parents[$oxobjectid] = 1;
   }
  $oTemp = new stdClass();    // #663
  $oTemp->cnt = $rs->fields['cnt'];
  $oTemp->id  = $oxobjectid;
  $oTemp->parent = $oxparentid;
  $aList[] = $oTemp;
  $rs->moveNext();
  }
 }
 return $aList;
}

_generateSimListSearchStr

Hier wird letztlich das finale SQL-Statement zusammengebaut. Auch hier muss nochmal berücksichtigt werden, welche Artikel nun Varianten und welche keine sind.

protected function _generateSimListSearchStr($sArticleTable, $aList) {
 $myConfig = $this->getConfig();
 $sFieldList = $this->getSelectFields();
 $sSearch = "select $sFieldList from $sArticleTable where ".$this->getSqlActiveSnippet()." and $sArticleTable.oxid in ( ";
 $blSep = false;
 $iCnt = 0;
 $oDb = oxDb::getDb();
 foreach ( $aList as $oTemp) {
  if ( $blSep) {
   $sSearch .= ',';
  }
  if($oTemp->parent) {
   $sSearch .= $oDb->quote($oTemp->parent);
  } else {
   $sSearch .= $oDb->quote($oTemp->id);
  }
  $blSep = true;
  if ( $iCnt >= $myConfig->getConfigParam( 'iNrofSimilarArticles' ) ) {
   break;
  }
  $iCnt++;
 }

 //#1741T
 //$sSearch .= ") and $sArticleTable.oxparentid = '' ";
 $sSearch .= ') ';

 // #524A -- randomizing articles in attribute list
 $sSearch .= ' order by rand() ';
 return $sSearch;
} 

Fazit – Produkte ein bisschen ähnlicher

Wenn jetzt alles funktioniert hat, dann sollten Dir Produkte angezeigt werden, die deutlich mehr Sinn ergeben. Je mehr Attribute mit sinnvollen Werten vergeben sind, umso bessere Ergebnisse wird die neue Implementierung ausspucken. Bitte achte darauf, dass die oben gezeigte Implementierung für diesen speziellen Fall funktioniert hat. Dass es eine allgemeingültige Lösung ist, glaube ich nicht. Es wird immer Produktkonstellationen geben, die automatische Berechnungen sprengen. In dem Fall kommt man nicht umhin den Code nochmal anzupassen. Allerdings ist die oben gezeigte Lösung vermutlich in annähernd allen Fällen besser, also die OXID Standard Implementierung.

Solltet ihr den Code erfolgreich einsetzen oder Verbesserungsvorschläge haben, dann schreibt doch einfach ein paar Zeilen als Kommentar auf diesem Beitrag.

1 Comments

Leave a Comment.