12

I'm building a "Product of the Month" block for the footer. It should load a category's products and display the first one.

This is my template file custom/featured-product.phtml:

<?php $_productCollection = $this->getLoadedProductCollection() ?>

<div class="featured-product">
    <h2><?php echo $this->__('Product of the Month') ?></h2>

    <?php foreach ($_productCollection as $_product): ?>
        <div class="item">
            <a class="product-image" href="<?php echo $_product->getProductUrl() ?>">
                <img src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize(200); ?>" alt="<?php echo $this->htmlEscape($this->getImageLabel($_product, 'small_image')) ?>" />
            </a>

            <a class="product-name" href="<?php echo $_product->getProductUrl() ?>"><?php echo $this->htmlEscape($_product->getName()) ?></a>

            <?php echo $this->getPriceHtml($_product, true) ?>
        </div>

        <?php
        // Note: Exit after first product.
        break;
        ?>
    <?php endforeach ?>
</div>

It's just a simplified version of Magento's product list template: catalog/product/list.phtml


WORKING

When embedding the block in a CMS Page, it works fine. Example:

{{block type="catalog/product_list" category_id="13" template="custom/featured-product.phtml" }}


NOT WORKING

When embedding the block via local.xml, it fails. The correct markup is returned but the specified category is not loaded. Instead a random (I don't how they're selected) set of products is loaded.

My code in local.xml:

<default>
    <reference name="footer">
        <block type="catalog/product_list" name="custom.featuredProduct" as="product_of_the_month" category_id="13" template="custom/featured-product.phtml" />
    </reference>
</default>

For completeness, I am rendering the block explicitly in page/html/footer.phtml like so:

<?php echo $this->getChildHtml('product_of_the_month') ?>


Any ideas?

My best guess is my local.xml is incorrect. Is there a parent block I need to load?


[Updates]

My original code crashes the product page. The workaround is not basing the code so heavily on the Magento core file: catalog/product/list.phtml. Specifically avoiding this line:

<?php $_productCollection = $this->getLoadedProductCollection() ?>


[Solution]

A working version with examples for use in CMS Pages and LayoutXML is included here: https://stackoverflow.com/a/12288000/1497746

Community
  • 1
  • 1
Brendan Falkowski
  • 733
  • 1
  • 5
  • 17

3 Answers3

12

Found a working solution using Alan Storm's advice.

/template/custom/featured-product.phtml

<?php
$_categoryId = $this->getCategoryId();

$_productCollection = Mage::getModel('catalog/category')->load($_categoryId)
    ->getProductCollection()
    ->addAttributeToSelect('*')
    ->addAttributeToFilter('status', 1)
    ->addAttributeToFilter('visibility', 4)
    ->setOrder('price', 'ASC');
?>

<div class="featured-product">
    <h2><?php echo $this->__( $this->getLabel() ); ?></h2>

    <?php foreach ($_productCollection as $_product): ?>
        <div class="item">
            <a class="product-image" href="<?php echo $_product->getProductUrl() ?>">
                <img src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize(200); ?>" alt="<?php echo $this->htmlEscape($this->getImageLabel($_product, 'small_image')) ?>" />
            </a>

            <a class="product-name" href="<?php echo $_product->getProductUrl() ?>"><?php echo $this->htmlEscape($_product->getName()) ?></a>

            <?php echo $this->getPriceHtml($_product, true) ?>
        </div>

        <?php
        // Note: Exit after first product.
        break;
        ?>
    <?php endforeach ?>
</div>

In short, the collection is manually generated rather than receiving a collection (as my initial attempt did):

<?php $_productCollection = $this->getLoadedProductCollection() ?>
<?php $_collectionSize = $_productCollection->count(); ?>


Using in a CMS Page:

{{block type="core/template" category_id="13" label="Product of the Month" template="custom/featured-product.phtml" }}


Using in a template:

/layout/local.xml

<default>
    <reference name="footer">
        <block type="core/template" name="custom.featuredProduct" as="featured_product" template="custom/featured-product.phtml">
            <action method="setData"><key>category_id</key><value>13</value></action>
            <action method="setData"><key>label</key><value>Product of the Month</value></action>
        </block>
    </reference>
</default>

/template/page/html/footer.phtml

<?php echo $this->getChildHtml('featured_product') ?>


Helpful resources:

How to get a product collection:

Using magic getters/setters:

Community
  • 1
  • 1
Brendan Falkowski
  • 733
  • 1
  • 5
  • 17
5

First, I've had random problems over the years using layout update xml attribute nodes to set values on blocks (other than template, as, name, type, or class, so trying something like this

<default>
    <reference name="footer">
        <block type="catalog/product_list" name="custom.featuredProduct" as="product_of_the_month" template="custom/featured-product.phtml">
            <action method="setCategoryId"><id>13</id></action>
        </block>
    </reference>
</default>

or this

<default>
    <reference name="footer">
        <block type="catalog/product_list" name="custom.featuredProduct" as="product_of_the_month" template="custom/featured-product.phtml">
            <action method="setData"><key>category_id</key><value>13</value></action>
        </block>
    </reference>
</default>

may help, and would be my first step.

After that, I'd go look at the block code that's loading the collection

#File: app/code/core/Mage/Catalog/Block/Product/List.php
class Mage_Catalog_Block_Product_List extends Mage_Catalog_Block_Product_Abstract
{
    ...
    public function getLoadedProductCollection()
    {
        return $this->_getProductCollection();
    }        
    ...
    protected function _getProductCollection()
    {
        if (is_null($this->_productCollection)) {
            $layer = $this->getLayer();
            /* @var $layer Mage_Catalog_Model_Layer */
            if ($this->getShowRootCategory()) {
                $this->setCategoryId(Mage::app()->getStore()->getRootCategoryId());
            }

            // if this is a product view page
            if (Mage::registry('product')) {
                // get collection of categories this product is associated with
                $categories = Mage::registry('product')->getCategoryCollection()
                    ->setPage(1, 1)
                    ->load();
                // if the product is associated with any category
                if ($categories->count()) {
                    // show products from this category
                    $this->setCategoryId(current($categories->getIterator()));
                }
            }

            $origCategory = null;
            if ($this->getCategoryId()) {
                $category = Mage::getModel('catalog/category')->load($this->getCategoryId());
                if ($category->getId()) {
                    $origCategory = $layer->getCurrentCategory();
                    $layer->setCurrentCategory($category);
                }
            }
            $this->_productCollection = $layer->getProductCollection();

            $this->prepareSortableFieldsByCategory($layer->getCurrentCategory());

            if ($origCategory) {
                $layer->setCurrentCategory($origCategory);
            }
        }

        return $this->_productCollection;
    }                
}

The getLoadedProductCollection method wraps a call to _getProductCollection, and _getProductCollection is where the collection is actually loaded.

So, some temporary debugging code in

protected function _getProductCollection()
{
    var_dump(__METHOD__);
    var_dump($this->getCategoryId());
    Mage::Log(__METHOD__);
    Mage::Log($this->getCategoryId());
}

Can help ensure your category id is making it from the layout update XML to the block.

However, if you look a little deeper at _getProductCollection, you'll notice that there's a few conditions where it resets the category ID.

if ($this->getShowRootCategory()) {
    $this->setCategoryId(Mage::app()->getStore()->getRootCategoryId());
}
...
if (Mage::registry('product')) {
    // get collection of categories this product is associated with
    $categories = Mage::registry('product')->getCategoryCollection()
        ->setPage(1, 1)
        ->load();
    // if the product is associated with any category
    if ($categories->count()) {
        // show products from this category
        $this->setCategoryId(current($categories->getIterator()));
    }
}
...

Is some other piece of Magento code has set the show_root_category property, or you're on a page where there's a product object in the registry, Magento will override your category id.

Making things even more complicated, once the collection is loaded it's set on a protected property

$this->_productCollection = $layer->getProductCollection();

that has no public getter method.

The ways to proceed here are myriad. If it were me I'd consider one of the following

  1. Using a custom block class that extends Mage_Catalog_Block_Product_List and has a method for resetting the category on a collection or loading a new collection

  2. Loading the collection myself, without relying on the code in product/list

Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • The `13` method and `category_id13` method work on *CMS Pages* and *Catalog View*. An exception is thrown on *Product View*. Investigating... – Brendan Falkowski Sep 05 '12 at 17:17
  • Magento *does* appear to override the collection on *Product View* causing an exception I don't know how to trace, so I moved onto your final two suggestions. #2 worked for me. Will post as an answer shortly. – Brendan Falkowski Sep 05 '12 at 18:36
1

I successfully recreated the problem under Magento CE 1.7.0.2.

First I created a local.xml with this content:

<default>
    <reference name="footer">
        <block type="catalog/product_list" name="custom.featuredProduct" as="product_of_the_month" category_id="13" template="custom/featured-product.phtml" />
    </reference>
</default>

I figured out, that some wrapping XML elements are missing and added some extra lines:

<?xml version="1.0"?>
<layout>
    <default>
        <reference name="footer">
            <block type="catalog/product_list" name="custom.featuredProduct" as="product_of_the_month" category_id="13" template="custom/featured-product.phtml" />
        </reference>
    </default>
</layout>

After adding the needed XML elements it worked.

ceckoslab
  • 1,189
  • 1
  • 9
  • 19
  • — Good idea, but my `local.xml` includes these also. I only posted the abridged, relevant code. – Brendan Falkowski Sep 05 '12 at 17:07
  • My be you have some special case. Just for the record: I tested it only for home page. Also I created dummy theme where put the local.xml and custom/featured-product.phtml No extensions installed. Products sample data was used and I only changed the category id. – ceckoslab Sep 05 '12 at 17:13
  • Did it load the right product collection from the specified category? – Brendan Falkowski Sep 05 '12 at 17:15