Magento-Workaround: Kategorien eines inaktiven Stores ausgeben

Das Problem

Bei einem aktuellen Projekt sind wir auf das Probem gestoßen, dass wir eine vollwertige CategoryCollection für ein Aufklapp-Menü von allen Stores benötigen – auch die, die nicht in Mage::app() als aktiv gesetzt sind.

In Magento kann immer nur ein Store aktiv sein. Zu diesem Store bekommt man ohne Probleme die Objekte für die Namen, Bilder, URLs usw.
Versucht man jedoch aus einem inaktiven Store eine CategoryCollection zu erhalten, bekommt man bei eingeschaltetem Rewrites einen Link mit dem aktiven Store-Code und nicht den, dem die Kategorie eigentlich zugeordnet ist.
Somit führen die URLs zu einem 404-Error.

Die Lösung

Die Lösung liegt in der Erweiterung des Mage_Catalog_Model_Category Models und des Mage_Catalog_Block_Navigation Models:

Zuerst erstellen wir uns ein Modul, um die Updatefähigkeit von Magento zu erhalten. Dazu legen wir unter app/code/local/Brainworxx/Catalog mit den Unterordnern Block, etc und Model an. Als nächstes erstellen wir folgende Dateien:

app/code/local/Brainworxx/Catalog/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Brainworxx_Catalog>
            <version>0.0.1</version>
        </Brainworxx_Catalog>
    </modules>

    <global>
        <blocks>
            <catalog>
                <rewrite>
	                <navigation>Brainworxx_Catalog_Block_Navigation</navigation>
                </rewrite>
            </catalog>
        </blocks>
        <models>
            <catalog>
                <rewrite>
                    <category>Brainworxx_Catalog_Model_Category</category>
                </rewrite>
            </catalog>
        </models>
        <helpers>
            <catalog>
                <rewrite>
                    <category>Brainworxx_Catalog_Helper_Category</category>
                </rewrite>
            </catalog>
        </helpers>
    </global>
</config>

app/etc/modules/Brainworxx_Catalog.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Brainworxx_Catalog>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Catalog />
            </depends>
        </Brainworxx_Catalog>
    </modules>
</config>

Damit wäre dann das Modul eingebunden und Konfiguration zum Überschreiben der Klassen Mage_Catalog_Block_Navigation und Mage_Catalog_Model_Category fertig. Nun fehlt noch die eigentliche Veränderung dieser Dateien.

app/code/local/Brainworxx/Catalog/Model/Category.php

<?php
class Brainworxx_Catalog_Model_Category extends Mage_Catalog_Model_Category
{
    /**
     * Get category url
     *
     * @param int $store specific store id for correct store-category urls
     * @return string
     */
    public function getUrl($store = null)
    {
        /**
         * Sicherstellen, dass $storeId vom Typ null oder int ist
         */
        $storeId = $store === null ? null : intval($store);
        $url = $this->_getData('url');
        if (is_null($url)) {
            Varien_Profiler::start('REWRITE: '.__METHOD__);

            if ($this->hasData('request_path') && $this->getRequestPath() != '') {
                /**
                 * ->setStore(Mage::app()->getStore($storeId)) sorgt dafür, dass die der Funktion übergebenen
                 * $store Id für die Url Instanz gesetzt wird
                 */
                $this->setData('url', $this->getUrlInstance()->setStore(Mage::app()->getStore($storeId))->getDirectUrl($this->getRequestPath()));
                Varien_Profiler::stop('REWRITE: '.__METHOD__);
                return $this->getData('url');
            }

            Varien_Profiler::stop('REWRITE: '.__METHOD__);

            $rewrite = $this->getUrlRewrite();
            /**
             * Hier setzen wir auch für das Rewrite -Modul
             * den korrekten Store
             */
            $rewrite->setStoreId($storeId);

            $idPath = 'category/' . $this->getId();
            $rewrite->loadByIdPath($idPath);

            if ($rewrite->getId()) {
                /**
                 * Hier passiert dasselbe wie oben
                 * Falls $store null ist, wird die Url wie
                 * sonst auch aufgebaut um alte getUrl aufrufe
                 * zu erhalten
                 */
                if($store === null) {
                    $this->setData('url', $this->getUrlInstance()->getDirectUrl($rewrite->getRequestPath())); //original codeline
                } else {
                    $this->setData('url', $this->getUrlInstance()->setStore(Mage::app()->getStore($storeId))
                            ->getDirectUrl($rewrite->getRequestPath()));
                }
                Varien_Profiler::stop('REWRITE: '.__METHOD__);
                return $this->getData('url');
            }

            Varien_Profiler::stop('REWRITE: '.__METHOD__);

            $this->setData('url', $this->getCategoryIdUrl());
            return $this->getData('url');
        }
        return $url;
    }
}

Jetzt fehlt nur noch die Übergabe der richtigen Store-Ids an der benötigten Stelle und wir bekommen die korrekte URL von inaktiven Store-Kategorien.

app/code/local/Brainworxx/Catalog/Block/Navigation.php

<?php
class Brainworxx_Catalog_Block_Navigation extends Mage_Catalog_Block_Navigation
{
    /**
     * Returns categories for every
     * store in an array with ready to insert
     * html in it
     *
     * @param int Level number for list item class to start from
     * @param string Extra class of outermost list items
     * @param string If specified wraps children list in div with this class
     * @return array
     */
    public function renderStoreCategories($level = 0, $outermostItemClass = '', $childrenWrapClass = '') {
        $websiteGroups = Mage::app()->getWebsite()->getStores();
        foreach( $websiteGroups as $id => $store ) {
            $rootIds = $store->getRootCategoryId();
            $categoryChildren = array();
            $helper = Mage::helper('catalog/category');
            $categoryChildren = $helper->getSpecificStoreCategories($store->getRootCategoryId(), $id);

            $activeCategoriesCount = count($categoryChildren);
            $hasActiveCategoriesCount = ($activeCategoriesCount > 0);

            $html[$id] = '';
            if ($hasActiveCategoriesCount) {
                $j = 0;
                foreach ($categoryChildren as $category) {
                    $html[$id] .= $this->_renderCategoryMenuItemHtml(
                        $category,
                        $level,
                        ( $j == $activeCategoriesCount - 1 ),
                        ( $j == 0 ),
                        true,
                        $outermostItemClass,
                        $childrenWrapClass,
                        true,
                        $id //ÜBERGABE DER STORE ID
                    );
                    $j++;
                }
            }
        }
        return $html;
    }

    /**
     * Returns full featured rewrite url
     * for an store category which is not
     * in the active store
     *
     * @param Mage_Catalog_Model_Category $category
     * @param int store store id in which the category is present
     * @return string
     */
    public function getFullStoreCategoryUrl($category, $storeId)
    {
        if (!$category instanceof Mage_Catalog_Model_Category) {
            $category =  Mage::getModel('catalog/category')->setStoreId($storeId)->load($category->getId());
        }
        $url = $category->getUrl($storeId);
        return $url;
    }

    /**
     * Render category to html
     *
     * @param Mage_Catalog_Model_Category $category
     * @param int Nesting level number
     * @param boolean Whether ot not this item is last, affects list item class
     * @param boolean Whether ot not this item is first, affects list item class
     * @param boolean Whether ot not this item is outermost, affects list item class
     * @param string Extra class of outermost list items
     * @param string If specified wraps children list in div with this class
     * @param boolean Whether ot not to add on* attributes to list item
     * @param int store id
     * @return string
     */
    protected function _renderCategoryMenuItemHtml($category, $level = 0, $isLast = false, $isFirst = false,
        $isOutermost = false, $outermostItemClass = '', $childrenWrapClass = '', $noEventAttributes = false, $store)
    {
        if (!$category->getIsActive()) {
            return '';
        }
        $html = array();

        // get all children
        if (Mage::helper('catalog/category_flat')->isEnabled()) {
            $children = (array)$category->getChildrenNodes();
            $childrenCount = count($children);
        } else {
            $children = $category->getChildren();
            $childrenCount = count($children); //von $children->count() geändert
        }
        $hasChildren = ($children && $childrenCount);

        // select active children
        $activeChildren = array();
        foreach ($children as $child) {
            if ($child->getIsActive()) {
                $activeChildren[] = $child;
            }
        }
        $activeChildrenCount = count($activeChildren);
        $hasActiveChildren = ($activeChildrenCount > 0);

        // prepare list item html classes
        $classes = array();
        $classes[] = 'level' . $level;
        $classes[] = 'nav-' . $this->_getItemPosition($level);
        if ($this->isCategoryActive($category)) {
            $classes[] = 'active';
        }
        $linkClass = '';
        if ($isOutermost && $outermostItemClass) {
            $classes[] = $outermostItemClass;
            $linkClass = ' class="'.$outermostItemClass.'"';
        }
        if ($isFirst) {
            $classes[] = 'first';
        }
        if ($isLast) {
            $classes[] = 'last';
        }
        if ($hasActiveChildren) {
            $classes[] = 'parent';
        }

        // prepare list item attributes
        $attributes = array();
        if (count($classes) > 0) {
            $attributes['class'] = implode(' ', $classes);
        }
        if ($hasActiveChildren && !$noEventAttributes) {
             $attributes['onmouseover'] = 'toggleMenu(this,1)';
             $attributes['onmouseout'] = 'toggleMenu(this,0)';
        }

        // assemble list item with attributes
        $htmlLi = '<li';
        foreach ($attributes as $attrName => $attrValue) {
            $htmlLi .= ' ' . $attrName . '="' . str_replace('"', '\"', $attrValue) . '"';
        }
        $htmlLi .= '>';
        $html[] = $htmlLi;
        $html[] = '<a href="'.$this->getFullStoreCategoryUrl($category, $store).'"'.$linkClass.'>';
        $html[] = '<span>' . $this->escapeHtml($category->getName()) . '</span>';
        $html[] = '</a>';

        // render children
        $htmlChildren = '';
        $j = 0;
        foreach ($activeChildren as $child) {
            $htmlChildren .= $this->_renderCategoryMenuItemHtml(
                $child,
                ($level + 1),
                ($j == $activeChildrenCount - 1),
                ($j == 0),
                false,
                $outermostItemClass,
                $childrenWrapClass,
                $noEventAttributes
            );
            $j++;
        }
        if (!empty($htmlChildren)) {
            if ($childrenWrapClass) {
                $html[] = '<div class="' . $childrenWrapClass . '">';
            }
            $html[] = '<ul class="level' . $level . '">';
            $html[] = $htmlChildren;
            $html[] = '</ul>';
            if ($childrenWrapClass) {
                $html[] = '</div>';
            }
        }

        $html[] = '</li>';

        $html = implode("\n", $html);
        return $html;
    }
}

Diese Methode basiert auf der renderCategoriesMenuHtml-Methode aus der Mage-Klasse, welche wir erweitern.
Der Unterschied: Wir gehen den Prozess für jeden Store durch und bauen uns so mit Hilfe von _renderCategoryMenuItemHtml() je ein Menü auf. _renderCategoryMenuItemHtml() bekommt als letzten Parameter die Store-Id übergeben und so kann mittels der neuen getFullStoreCategoryUrl($category, $store)-Methode diese Store-Id an unsere überschriebene getUrl-Methode übergeben werden.
Damit renderStoreCategories() funktionieren kann, müssen wir noch einen Helper mit dieser Funktion anlegen.
Im Grunde ist dies auch nur eine Erweiterung der schon vorhandenen getStoreCategories() aus dem Mage_Catalog_Helper_Category. Der einzige Unterschied ist, dass wir hier immer die gerade zu verarbeitende StoreId und dessen Root Kategorie mitgeben, damit jeweils der richtige Kategoriebaum aufgebaut wird.
Deshalb überschreiben wir diesen Helper mit folgender Klasse:

app/code/local/Brainworxx/Catalog/Helper/Category.php

<?php
/**
 * Extended catalog category helper
 *
 * @category   Brainworxx
 * @package    Brainworxx_Catalog
 */
class Brainworxx_Catalog_Helper_Category extends Mage_Catalog_Helper_Category
{
    /**
     * Retrieves store categories
     * from a specific store
     *
     * @param   int $parentCategoryId
     * @param   int $storeId
     * @param   boolean|string $sorted
     * @param   boolean $asCollection
     * @return  Varien_Data_Tree_Node_Collection|Mage_Catalog_Model_Resource_Eav_Mysql4_Category_Collection|array
     */
    public function getSpecificStoreCategories($parentCategoryId, $storeId, $sorted=false, $asCollection=false, $toLoad=true)
    {
        $store = Mage::app()->getStore($storeId);
        $cacheKey   = sprintf('%d-%d-%d-%d', $parentCategoryId, $sorted, $asCollection, $toLoad);
        if (isset($this->_storeCategories[$cacheKey])) {
            return $this->_storeCategories[$cacheKey];
        }

        /**
         * Check if parent node of the store still exists
         */
        $category = Mage::getModel('catalog/category');
        
        /* @var $category Mage_Catalog_Model_Category */
        if (!$category->checkId($parentCategoryId)) {
            if ($asCollection) {
                return new Varien_Data_Collection();
            }
            return array();
        }

        $recursionLevel  = max(0, (int) Mage::app()->getStore()->getConfig('catalog/navigation/max_depth'));
        $storeCategories = $category->getCategories($parentCategoryId, $recursionLevel, $sorted, $asCollection, $toLoad);

        $this->_storeCategories[$cacheKey] = $storeCategories;
        return $storeCategories;
    }
}

Mit folgendem Code bekommen wir nun im gewünschten Template ein Menü aller Stores mit ihren Kategorien:

<?php $_menu = $this->renderStoreCategories(0,'level-top') ?>
<?php if($_menu): ?>
    <?php foreach ( $_menu as $id => $_storemenu ): ?>
        <div class="nav-container" id="stores-category-menu-<?php echo $id ?>" style="display:none;">
            <?php if( $_storemenu != '' ): ?>
                <ul class="nav">
                    <?php echo $_storemenu ?>
                </ul>
            <?php endif ?>
        </div>
    <?php endforeach ?>
<?php endif ?>

Und hier die Layoutanweisung, um dem Template den richtigen Block zuzuweisen. Diese muss an gewünschter Stelle in der passenden xml-Datei in dem eingestellten Design
eingebunden werden.

<block type="catalog/navigation" name="catalog.topnav" template="pfad/zum/template.phtml"/>

Die Struktur des gesamten Moduls ist nun folgendermaßen:
Ordnerstruktur

  • Diesen Beitrag weiterempfehlen:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Lösen Sie bitte die Rechenaufgabe. *