Showing all reviews and ratings on a page in Magento
Finally, a new post, and at last it is about Mangeto Commerce (http://www.magentocommerce.com/). In my first Magento how-to you’ll learn how to retrieve all product reviews and show them on a single page, together with the average rating. For this, I assume you have already created a new module and are able to view the page. You’ll only need to manipulate a block and a template.
First, let’s retrieve the reviews collection (this method will go into the block):
function getReviews() {
$reviews = Mage::getModel('review/review')->getResourceCollection();
$reviews->addStoreFilter( Mage::app()->getStore()->getId() )
->addStatusFilter( Mage_Review_Model_Review::STATUS_APPROVED )
->setDateOrder()
->addRateVotes()
->load();
return $reviews;
}
We’re using Mage_Review_Model_Mysql4_Review_Collection, which is a resource model. First setup the collection, filtering by store – we only want to retrieve the product reviews in the current store -, by status – show only approved reviews -, and ordering by date in reverse order, then load the collection.
addRateVotes() helps loading all the ratings/votes for that review. We’re gonna use this collection to compute the average rating.
Next, let’s move on to the template for a second. We’re gonna call the getReviews() method, then iterate through all the reviews. For each review you would probably want to display the title, nickname, date and details, but also the product associated and the user rating. For the first four, things are pretty easy, all you have to do is call getTitle(), getNickname(), getDetail(), getCreatedAt() on each review object.
To display the product name & link, we need to retrieve the product associated with each review – unfortunately I wasn’t able to find a way to join the product tables inside the query for retrieving all the reviews. So, we need to create a helper method inside our block, called getProduct(). We’re gonna use a storage/registry variable called _loadedProducts, so that we avoid loading the same product multiple times.
function getProduct( Mage_Review_Model_Review $review ) {
if( !isset($this->_loadedProducts[ $review->getEntityPkValue() ]) ) {
$this->_loadedProducts[$review->getEntityPkValue()] = Mage::getModel('catalog/product')->load( $review->getEntityPkValue() );
}
return $this->_loadedProducts[ $review->getEntityPkValue() ];
}
And inside the template:
<?php $_prod = $this->getProduct( $review ); ?> <a href="<?php echo $_prod->getProductUrl(); ?>"><?php echo $_prod->getName(); ?></a>
One last thing, if you intend to display the average rating of each review, add another helper method inside the block:
function getAverageRating( Mage_Review_Model_Review $review ) {
$avg = 0;
if( count($review->getRatingVotes()) ) {
$ratings = array();
foreach( $review->getRatingVotes() as $rating ) {
$ratings[] = $rating->getPercent();
}
$avg = array_sum($ratings)/count($ratings);
}
return $avg;
}
And then call it in the template (in this example we’re using Magento’s default styling):
<div class="rating-box"> <div class="rating" style="width: <?php echo ceil($this->getAverageRating( $review )); ?>%;"></div> </div>
That is all! You now have a page where all product reviews can be display.
Things to consider: pagination and cache!
Update: Seems that Magento comes prepared for reviews on products, categories and customers. We only need to load product reviews, so it would be wise to filter by entity. Unfortunately, the current version of Magento doesn’t allow filtering for a certain entity, only by entity and entity PK (which is the product ID in this case). Of course, we could write a decorator and write a method to just add a filter for entity_code = 'product', but the quickest (and dirtiest) way of doing it is by adding a check inside the template foreach loop (or adding a helper method in the block):
if( $review->getEntityId() == 1 ) { continue; }
//1 is the id of the 'product' entity - if you write a method, use a class constant
I am trying to install this but I am getting an error (see below). I think I am real close, so any help is much appreciated. AP_CustReview_Block_View is my mod obviously – seems to be an issue with the _loadedProducts var:
Notice: Indirect modification of overloaded property AP_CustReview_Block_View::$_loadedProducts has no effect in …
Have you tried googling for that notice? It’s a PHP error, not Magento.
It’s hard to say what the cause of your problem is without having the full code
and its purposeon my screen. See if this helpsI did google that with no success. Here is my code.
View.php (block):
<?php class AP_CustReview_Block_View extends Mage_Core_Block_Template { function getReviews() { $reviews = Mage::getModel('review/review')->getResourceCollection(); $reviews->addStoreFilter( Mage::app()->getStore()->getId() ) ->addStatusFilter( Mage_Review_Model_Review::STATUS_APPROVED ) ->setDateOrder() ->addRateVotes() ->load(); return $reviews; } function getProduct( Mage_Review_Model_Review $review ) { if( !isset($this->_loadedProducts[ $review->getEntityPkValue() ]) ) { $this->_loadedProducts[$review->getEntityPkValue()] = Mage::getModel('catalog/product')->load($review->getEntityPkValue()); } return $this->_loadedProducts[$review->getEntityPkValue()]; } } ?>view.phtml (template):
<?php $reviews = $this->getReviews() ?> <?php foreach ($reviews as $review) { ?> <?php if( $review->getEntityId() == 1 ) { ?> <?php $_prod = $this->getProduct( $review ); ?> <a href="<?php echo $_prod->getProductUrl(); ?>"><?php echo $_prod->getName(); ?></a> <?php } } ?>I am using a cms page to call everything in. I know the mod setup works – already tested that. Once I have a review on the site, it breaks and gives me that error I sent in my last post. Thank you for your help.
I think I’ve reproduced the issue – try defining
$_loadedProductsinAP_CustReview_Block_View– ideally as protected or private.That was it – don’t know why I didnt see that before – my bad. Thanks again for your help.
@Blake
Hi,
I’ve pasted the code of Blake in app/code/core/Mage/Catalog/Block/Product/View.php and I saw in the post above that I have to define $_loadedProducts. Unfortunately I don’t know how to do this.
A sample of my error:
Warning: Invalid argument supplied for foreach() in /var/www/vhosts/***.nl/subdomains/***/httpdocs/app/design/frontend/default/default/template/page/html/***.phtml on line 2
Line 2 in this file is this part:
Is there anyone who can help me with this problem?
Thanks in advance!
To define a class attribute you should just write something like
protected $_loadedProducts;in your class declaration (in app/code/core/Mage/Catalog/Block/Product/View.php).The warning you receive happens when the argument supplied to foreach is not an array (could be null, int, string, float, but not an array).
Hello firewizard,
Thanks for the above code.It was very helpful.But my concern is regarding the pagination. I have tried it from my side.No success yet.If you have any solution please let me know.
Thanks
Thanks Raul – glad it was useful. Regarding pagination, I must admin I haven’t tried it yet, but off the top of my head I’d say there are a couple of ways of doing it:
- using the limit() method which applied to all Varien_Collection_Db
- using some Paginator class – I know there are classes for pagination (including the default one, shipped with ZF)
… and it just hit me, maybe it also works by using the toolbar classes (catalog product list toolbar)
Thanks for your reply…
/*
$toolbar = this->getLayout()->createBlock(‘catalog/product_list_toolbar’, microtime());
$toolbar->setCollection($yourCollection);
echo $toolbar->toHtml();
*/
I have used the above code and got the pagination tool bar, but still want the paramater to be passed to the reviews collection so that it works….Right now its displaying all the reviews with the pagination toolbar..
Might be some parameter need to be passed to he review collection so that it works and shows records according to pagination toolbar…
Try using Mage_Page_Block_Html_Pager – its purpose is to paginate collections. You’ll have to write some code in your controller to pass the page (and maybe limit too) to the block and/or model.
This is useful for pagination, although the class I mentioned does all the work for you.
Magento Collections have a cool feature – it comes with two methods: getSize() and count(). The first one returns the total number of items while the second one returns the number of items withing the current collection (after limit)
Hi,
Thanks for this post helped me alot.
Has anyone here been able to limit the reviews made by customers. i.e. they can only review a product once and also he needs to have purchased/ordered that product before he can actually review/rate it.
Any help on this would be highly appreciated.
Cheers
I would try rewriting some parts of the Reviews module and adding some checkpoints like isLoggedIn, hasPreviousReview(customer ID or email), hasOrder(customer ID or email). It requires some coding, but I don’t see why it wouldn’t work…