我终于破解了它,尽管我并不相信这是一个最佳解决方案。无论如何,我将描述我在这里所做的事情,希望有人可以让它更有效率。因为我对很多领域都很熟悉,所以我将给出一个详细的描述。抱歉,篇幅较长。
正如我所说,至少在我的情况下,主菜单是通过 app/code/core/Mage/Page/Block/Html 中的 Topmenu.php 构建的,特别是方法 _getHtml。我绝对不想修改核心文件,所以我发现了如何通过新模块扩展此方法。 (如果您熟悉创建新模块,可以跳过这一点。)
配置新模块
我需要创建一个新模块(下面我将其称为 MYMOD)。当我覆盖核心 magento 页面块时,我必须创建新文件夹:app/code/local/MYMOD/Page 并在其中创建两个子文件夹 Block 等(我相信它们区分大小写)。而在 Block 另一个子文件夹 Html.您可以看到这完全反映了 app/code/core/Mage 中的文件夹结构。
etc 文件夹在 config.xml 文件中保存新模块的规范。这是我的样子:
<?xml version="1.0" encoding="UTF-8"?>
<!-- The root node for Magento module configuration -->
<config>
<!--
The module's node contains basic
information about each Magento module
-->
<modules>
<!--
This must exactly match the namespace and module's folder
names, with directory separators replaced by underscores
-->
<MYMOD_Page>
<!-- The version of our module, starting at 0.0.1 -->
<version>0.0.1</version>
</MYMOD_Page>
</modules>
<global>
<blocks>
<page>
<rewrite>
<html_topmenu>MYMOD_Page_Block_Html_Topmenu</html_topmenu>
</rewrite>
</page>
</blocks>
</global>
</config>
您可以在其他地方找到有关此问题的原因和原因。
不幸的是(在我看来!),这并不是在 Magento 中指定一个新模块所要做的全部。您还必须在 app/etc/modules 中创建一个名为“MYMOD_Page.xml”的文件。这只是告诉 Magento 你的模块以及在哪里寻找它。我的看起来像这样:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<MYMOD_Page>
<!-- Whether our module is active: true or false -->
<active>true</active>
<!-- Which code pool to use: core, community or local -->
<codePool>local</codePool>
</MYMOD_Page>
</modules>
</config>
好的,对于模块上不相关的说明,我很抱歉,但我确实喜欢独立的逐个解释。
覆盖方法
我现在可以在我的模块中创建一个带有子类的新文件,我可以在其中使用方法(函数)来代替核心的 Magento 方法。文件名必须与原始文件 Topmenu.php 相同,位于 app/code/local/MYMOD/Page/Block/Html。
请记住,面向对象的结构意味着我可以使用原始核心版本 Topmenu.php 中的所有功能,我不必在我的新版本中复制它们(非常便于维护)。我的 Topmenu.php 版本只需要包含我可能想要的任何新功能(在这种情况下我不需要任何功能)并重新声明我想用我自己的版本覆盖的任何功能。就我而言,我需要修改两个函数:_getHtml 和 _getMenuItemClasses。
_getHtml 需要额外检查,因此不包括任何空类别。
_getMenuItemClasses 需要额外的检查,以便“父级”类不会添加到子级为空的类别中。
这就是我的做法(使用 cmets)。我确信有更好的方法,但我对 Magento 还是很陌生。
class MYMOD_Page_Block_Html_Topmenu extends Mage_Page_Block_Html_Topmenu
// Create my subclass, in accordance with how I've defined the new module
{
/**
* Recursively generates top menu html from data that is specified in $menuTree
*
* @param Varien_Data_Tree_Node $menuTree
* @param string $childrenWrapClass
* @return string
*/
protected function _getHtml(Varien_Data_Tree_Node $menuTree, $childrenWrapClass)
{
$html = '';
$children = $menuTree->getChildren();
$parentLevel = $menuTree->getLevel();
$childLevel = is_null($parentLevel) ? 0 : $parentLevel + 1;
$counter = 1;
$childrenCount = $children->count();
$parentPositionClass = $menuTree->getPositionClass();
$itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-';
foreach ($children as $child) {
$child->setLevel($childLevel);
$child->setIsFirst($counter == 1);
$child->setIsLast($counter == $childrenCount);
$child->setPositionClass($itemPositionClassPrefix . $counter);
$outermostClassCode = '';
$outermostClass = $menuTree->getOutermostClass();
if ($childLevel == 0 && $outermostClass) {
$outermostClassCode = ' class="' . $outermostClass . '" ';
$child->setClass($outermostClass);
}
/*
* Find out if this category has any products. I don't know an easier way.
* The id of every child returned by getID is of the form "category-node-nnn"
* where nnn is the id that can be used to select the category details.
* substr strips everything leaving just the nnn.
* Then use getModel-> getCollection with a filter on the id. Although this looks
* like it will return many, obviously category ids are unique so in fact it only
* returns the category we're currently looking at.
*/
$_gcategoryId = substr($child->getId(), 14, 6);
$_gcategories = Mage::getModel('catalog/category')->getCollection()->addFieldToFilter('entity_id', array('eq', $_gcategoryId));
foreach ($_gcategories as $_gcategory) {
$_gcategoryCount = $_gcategory->getProductCount();
}
/*
* Now only include those categories that have products.
* In my case I also wanted to include the top level categories come what may.
*/
if (($childLevel == 0) || ($_gcategoryCount > 0)) {
$html .= '<li ' . $this->_getRenderedMenuItemAttributes($child) . '>';
$html .= '<a href="' . $child->getUrl() . '" ' . $outermostClassCode . '><span>'
. $this->escapeHtml($child->getName()) . '</span></a>';
if ($child->hasChildren()) {
if (!empty($childrenWrapClass)) {
$html .= '<div class="' . $childrenWrapClass . '">';
}
$html .= '<ul class="level' . $childLevel . '">';
$html .= $this->_getHtml($child, $childrenWrapClass);
$html .= '</ul>';
if (!empty($childrenWrapClass)) {
$html .= '</div>';
}
}
$html .= '</li>';
}
$counter++;
}
return $html;
}
/**
* Returns array of menu item's classes
*
* @param Varien_Data_Tree_Node $item
* @return array
*/
protected function _getMenuItemClasses(Varien_Data_Tree_Node $item)
{
$classes = array();
$classes[] = 'level' . $item->getLevel();
$classes[] = $item->getPositionClass();
if ($item->getIsFirst()) {
$classes[] = 'first';
}
if ($item->getIsActive()) {
$classes[] = 'active';
}
if ($item->getIsLast()) {
$classes[] = 'last';
}
if ($item->getClass()) {
$classes[] = $item->getClass();
}
if ($item->hasChildren()) {
/*
* Don't just check if there are children but, if there are, are they all empty?
* If so, then the changes in _getHtml will mean none of them will be included
* and so this one has no children displayed and so the "parent" class is not appropriate.
*/
$children = $item->getChildren(); // Get all the children from this menu category
foreach ($children as $child) { // Loop over each child and find out how many products (see _getHtml)
$_gcategoryId = substr($child->getId(), 14, 6);
$_gcategories = Mage::getModel('catalog/category')->getCollection()->addFieldToFilter('entity_id', array('eq', $_gcategoryId));
foreach ($_gcategories as $_gcategory) { // Remember, there's actually only one category that will match the child's id
$_gcategoryCount = $_gcategory->getProductCount();
}
if ($_gcategoryCount > 0) { // As soon as one child has products, then we have a parent and can stop looking
$classes[] = 'parent';
break;
}
}
}
return $classes;
}
}
我希望这很清楚。它可以满足我的需求(我的商店很小),但欢迎提出任何改进建议。