News:

You may pay someone to create your store, or you visit our seminar and become a professional yourself with the silver certification

Main Menu

How to merge all reviews (from childs and display it on parent product page)

Started by bart198x, April 06, 2015, 11:40:38 AM

Previous topic - Next topic

bart198x

Hello. As in subject.
Is there anyone, who maybe done this?

joomla 3.4.1 and VM 3.0.6.4
I'm interested in how to merge all parent's childs reviews and display them on parent product page, and that parent's childs pages too. Why?
I have parent product (not ordering), and child products which are size variant only (on generic child variant), so that childs are not categorised, but have reviews etc...
And I wish to display that reviews not only on variant product page but on all from parent's childs and parent (merge all of them to one big family but only on display, not in db - there is no need to rewrite them so many times)
I'm looking for how to get review in php files but I can't find that.
Maybe in VM configuration there is some option - but I can't find it.

ZoBabe

I also need this! The new multi-variant function is great, but it renders reviews useless. People would need to leave separate reviews for every size/color variant...  :-\
  I really need all child pages to just display the parent reviews (and have new reviews saved to the parent).
  Joomla: 3.4.1
  Virtuemart: 3.0.6.2
  Help!

bart198x

I think I get it.
VM 3.0.8
In file /temlplates/your_template/html/com_virtuemart/productdetails/default.php (it's for override as making changes to component's core file have no sense)
I decided to put code before line (about 218) BEFORE:
// event onContentBeforeDisplay
echo $this->product->event->beforeDisplayContent; ?>

<?php


paste:
//getting all reviews of parent product and childs
//getting ids of parent and childs - need to clean up this, and get datas from model or something but so far it works too
$parentID = $this->product->product_parent_id;
if ($parentID == 0) $parentID = $this->product->virtuemart_product_id;
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select($db->quoteName('virtuemart_product_id'));
$query->from($db->quoteName('#__virtuemart_products'));
$query->where($db->quoteName('product_parent_id') . ' = '. $parentID);
$db->setQuery($query);
$results = $db->loadObjectList();
$i=-1;
$ids_array = array();
foreach ($results as $result) {
if($i == -1) $ids_array[0] = $parentID;
$ids_array[]=$result->virtuemart_product_id;
$i++;
}
//end getting ids of parent and childs
// so far we have all ids collected
//var_dump($ids_array);

//getting reviews from all collected products id (parent and childs - we could exclude parent if it's not orderable, but...)
$ratingModel = VmModel::getModel('ratings');
$reviews_array = array();
foreach ($ids_array as $virtuemart_product_id) {
$reviews_array = array_merge($reviews_array, $ratingModel->getReviews($virtuemart_product_id));
}
$this->rating_reviews = $reviews_array;
//var_dump($this->rating_reviews);
//end getting reviews from all collected products id

//end getting all reviews of parent product and childs

I made it in view file, it's not too good i know but it works and prevent from vm updates overrides.
Maybe some smart head make a plugin for that or something.
Or maybe someone will put it to Virtuemart as config option :) would be nice.

version 2 :)
In file /temlplates/your_template/html/com_virtuemart/productdetails/default_reviews.php
about line 177 after
   } else if(!$review_editable) {
      echo '<div class="alert alert-info"><strong>'.vmText::_( 'COM_VIRTUEMART_DEAR' ).$this->user->name.',</strong><br />';
      echo vmText::_( 'COM_VIRTUEMART_REVIEW_ALREADYDONE' );
      echo '</div>';
   }
}

//getting all reviews of parent product and childs

//getting ids of parent and childs - need to clean up this, and get datas from model or something but so far it works too
$productModel = VmModel::getModel('product');
$idsArray[0] = $this->product->product_parent_id;
if ($idsArray[0] == 0) $idsArray[0] = $this->product->virtuemart_product_id;
$idsArray = array_merge($idsArray, $productModel->getProductChildIds($idsArray[0]));

//getting reviews from all collected products id (parent and childs - we could exclude parent if it's not orderable, but...)
$ratingModel = VmModel::getModel('ratings');
//$this->rating_reviews ='';
$reviews_array = array();
foreach ($idsArray as $product_id) {
$reviews_array = array_merge($reviews_array, $ratingModel->getReviews($product_id));
}
$this->rating_reviews = $reviews_array;
//end getting all reviews of parent product and childs

It can be add to default.php too (just need to experiment :) )

ps. In my case only there is a problem on trigering page breaks js script after clicking show more reviews but that will be repaired by template team(i hope so :) ) - I remove link show more reviews because in child variant  (external child variant plugin working like generic child variant) it gave me some weird values to page address... and break page. (sklep/najnowsza-kolekcja/bluzy/bluza-detal-3-detail?tmpl=component&showall=1    ;showall=1 is ok but what is the ?tmpl=component   ???? :)  )

Result can be checked on: http://test.belladonnafashion.pl/sklep/najnowsza-kolekcja/bluzy/bluza-detal-detail
There are two reviews for each variant, but they are all displayed in parent and childs.
Link will be available until I finish.
As anyone knows how to make it better, please write.
Best regards.

bart198x

I don't know if there is not some problem with that link 'show more reviews' because generic child variant without my changes make such error too ??? hmm. I have no time now to check that, I remove that link and that's it.

bart198x

I think I've done it.
Taking all ratings (from ultimate parent, childs, childs of childs... etc. even if you are in x child of ultimate parent, taken to it from some where) and display as stars on product detail and category view
Of course if template allows such possibility.
Can be checked on :
http://test.belladonnafashion.pl/sklep/najnowsza-kolekcja/bluzy/bluza-detal-detail
or:
http://test.belladonnafashion.pl/sklep/najnowsza-kolekcja/tuniki/sukienka-10-detail
or:
http://test.belladonnafashion.pl/sklep/najnowsza-kolekcja/bluzy
:)
For now all changes are done in templates/Jours_template/html/com_virtuemart/sublayouts/rating.php
after:

<?php defined('_JEXEC') or die('Restricted access');

$product $viewData['product'];

if (
$viewData['showRating']) {


paste:

function uParentID($product){
//getting ids of parent and childs
//getting ultimate parent id now matter how deep on child parent child parent... combination we are (possible in generic child variant) tested on deepth 3
$productModel = VmModel::getModel('product');
$productID = $product->virtuemart_product_id;
$parentID = $product->product_parent_id;
if($parentID == 0) $parentID = $productID; else {
while ($parentID != 0){
$product = $productModel->getProductParent($productID);
$parentID = $product->product_parent_id; //untouchable
if($parentID != 0) {
$productID = $parentID; //needed in next loop step - checking parent id of parent of...
}
}
}
//var_dump($parentID); //in this place we have ultimate parent ID no matter if we are in child of parent of another child of parent (possible in generic child variant)
// but not checking if is orderable... and published... yet
return $parentID;
}



//getting all published, orderable products ratings

$arr = array(uParentID($product));

function arrar_write( $arr, $index = '', &$all_products_ratings=array()){
$productModel = VmModel::getModel('product');
        ksort( $arr );
foreach( $arr as $k => $v ){
$product = $productModel->getProduct($v);
if($product->published === '1'){
if($product->orderable == '1') {
$ratingModel = VmModel::getModel('ratings');
$virtuemart_product_id=$v;
$productRating = $ratingModel->getReviews($virtuemart_product_id);
foreach($productRating as $rate){
if($rate->published === '1'){
$all_products_ratings[] = $rate->review_rates;
}
}
}
}
$v=$productModel->getProductChildIds($v);
            if( !empty($v) ){
                arrar_write( $v, $index .= $k , $all_products_ratings);
                $index = substr( $index, 0, -1 );
}
        }
return $all_products_ratings;
}

$all_products_ratings = arrar_write( $arr, $index = '', $all_products_ratings);
//var_dump($all_products_ratings);  //here we have all ratings, of first  parent and all his childs and grandies, in one array

//getting average from array
$rating_avg = 0;
function average($all_products_ratings, &$rating_avg) {
$count = count($all_products_ratings);

if($count>0) {
$rating_avg = array_sum($all_products_ratings) / $count;
$rating_avg = number_format((float)$rating_avg, 2, '.', '');
return $rating_avg;
}
else return $rating_avg;
}

$product->rating = average($all_products_ratings, $rating_avg);


tested on generic child variants and (customfieldsforall + stockablecustomfields; combination of plugins from same developer)
I'm still watching on that and changing something, if anyone know how make it easier PLEASE WRITE :)

Maybe some plugin?Perhaps one that connects those two possibilities (all reviews and ratings)?

Studio 42

Hi,
Your loop only get the parents.

I had a request for the inverse, this mean getting child review in parent. Now wath is the right way?
Displaying parent , sisters or child review or all ?
If you use the parent as a model, then getting review from parent is bad. This is not so easy as you mean.

bart198x

The third post is telling how I did it (Take all rewiews from all childs and display them on first parent site - it is included on that site too) but is not cleaned at all and lived as is, that code takes all reviews but unsorted unfortunately, mayby today I will join that two things, But as I see now You could be right so I will check that and correct (the ratings are ok recurent - rewiews can be wrong indeed they where not recurent, I will check that :) but after work about 17:00)

Studio 42

Yesy, you are right, your loop get all.
But the problem, implementing this in the core, mean you can set what you need:
- parents only
- childs only
- all.
But for a complicate shop, you need to check perhaps more

I know you use here the Virtuemart APi, but i think when you have 50 Child/parents your server will be on the knees.
I think, but certainly not milbo, using a direct mysql query is 10 times faster because here you have only to get ratings and reviews.

bart198x

1. AAA but that is the task for configurable plugin :) I wrote that because I need it working in such way (I need to check that reviews), and no one had it :) (or I just couldn't find - more or less I was wayting almost week i think).
Now is the time to clean this up and maybe make with result something useful :) If I somehow steal some time.
Sorting reviews would be niece too by date modyfication date by rating :)

2.Yes that's the target sql queries, but playing on view files I try to avoid sql queries, in plugin or component the most desirable, in view or sublayouts hmmm... I don't know. Yes queries would be faster.
that code is only idea, not the product at all.

Is there still no solution to override part of class?  :)

that first func generates some ajax errors wow hmm...

Now I think Iknow what You mean :) hehehe. Tommorow, now I'm beer drinker hehe :)

bart198x

I think it works now  (for rating not reviews)
Tested for tree: (I don't know if there are any mistakes)

I don't know actualy if DB query would be so much faster because there still have to be recursion (backward to get first parent, and forward to get all needed products ids), the difference would be that there will no be model calling but dynamically creation of query, but the loop steps will be the same, after that will be send just one dynamically created query which get from DB proper informations.
But that is just speculation...

There is only one point - firstly I created tree on same custome field 'proba' : parent CF 'proba'->child parent CF 'proba'->child parent CF 'proba'->child and that was wrong.
Then I create tree on three different custome fields 'proba' 'proba2' 'proba3': parent CF 'proba'->child parent CF 'proba2'->child parent CF 'proba3'->child and that works :)

corected code all file rating.php:
  templates/Jours_template/html/com_virtuemart/sublayouts/rating.php
<?php defined('_JEXEC') or die('Restricted access');

$product $viewData['product'];

if (
$viewData['showRating']) {

function 
getAverageRating($product){

//getting ultimate parent id now matter how deep on child parent child parent... 
//combination we are (possible in generic child variant) tested on deepth 3
function uParentID($product){
$productModel VmModel::getModel('product');
$productID $product->virtuemart_product_id;
$parentID $product->product_parent_id;
while ($parentID != 0){
$newproduct $productModel->getProduct($parentID);
$productID $newproduct->virtuemart_product_id;
$parentID $newproduct->product_parent_id//untouchable
}
//if($parentID == 0) $parentID = $productID;

//in this place we have ultimate parent ID no matter if we are in child of parent of another child of parent (possible in generic child variant)
// but not checking if is orderable... and published... yet
return $productID;
}

//getting all published, orderable products ratings

function arrar_write$arr$index '', &$all_products_ratings=array()){
$productModel VmModel::getModel('product');
        
ksort$arr );
foreach( $arr as $k => $v ){
$product $productModel->getProduct($v);
if($product->published === '1'){
if($product->orderable == '1') {
$ratingModel VmModel::getModel('ratings');
$virtuemart_product_id=$v;
$productRating $ratingModel->getReviews($virtuemart_product_id);
foreach($productRating as $rate){
if($rate->published === '1'){
$all_products_ratings[] = $rate->review_rates;
}
}
}
}
$v=$productModel->getProductChildIds($v);
            if( !empty(
$v) ){
                
arrar_write$v$index .= $k $all_products_ratings);
                
$index substr$index0, -);
}
        }
return $all_products_ratings;
}
//here we have all ratings, of first  parent and all his childs and grandies, in one array

//getting average from array
function average($all_products_ratings, &$rating_avg) {
$count count($all_products_ratings);
if($count>0) {
$rating_avg array_sum($all_products_ratings) / $count;
$rating_avg number_format((float)$rating_avg2'.''');
return $rating_avg;
}
else return $rating_avg;
}

// run all
$arr = array(uParentID($product));
$all_products_ratings arrar_write$arr$index ''$all_products_ratings);
$product->rating average($all_products_ratings$rating_avg);
$averageRating $product->rating;
return $averageRating;
}

//fire main function
$product->rating getAverageRating($product);


$maxrating VmConfig::get('vm_maximum_rating_scale'5);
if (empty($product->rating)) {
?>

<div class="ratingbox dummy" title="<?php echo vmText::_('COM_VIRTUEMART_UNRATED'); ?>" >

</div>
<?php
} else {
$ratingwidth $product->rating 14;
  
?>


<div title=" <?php echo (vmText::_("COM_VIRTUEMART_RATING_TITLE") . ' ' $product->rating '/' $maxrating?>" class="ratingbox" >
  <div class="stars-orange" style="width:<?php echo $ratingwidth.'px'?>"></div>
</div>
<?php
}
}

lindapowers

With the increasing custom fields, child products , multi variants this is something to think about.

The parent product in some cases is not sold and just acts as a selector for the childs, therefore is difficult that customers review it... and would be cool to show the reviews from the childs in the parent.

Studio 42

Perhaps the code to simplify and speedup your code:

function getAllReviews($virtuemart_product_id) {
// tree up
$q = 'SELECT T2.virtuemart_product_id
FROM (
SELECT
@r AS _id,
(SELECT @r := product_parent_id FROM j3vm3_virtuemart_products WHERE virtuemart_product_id = _id) AS product_parent_id,
@l := @l + 1 AS lvl
FROM
(SELECT @r := '.$virtuemart_product_id.', @l := 0) vars,
#__virtuemart_products m
WHERE @r <> 0) T1
JOIN j3vm3_virtuemart_products T2
ON T1._id = T2.virtuemart_product_id
ORDER BY T1.lvl DESC limit 0,1';
$db->setQuery($q);
$inparents = $db->loadResult();


//tree down
$ids = array();
while ($inparents) {
$ids[] = $inparents;
    $q ='select group_concat( virtuemart_product_id) as virtuemart_product_id from #__virtuemart_products where product_parent_id in ( '.$inparents.' )';
$db->setQuery($q);
$inparents = $db->loadResult();
}

// all reviews in one query
$q ='select `pr`.*,`p`.`product_name`,`rv`.`vote`, `u`.`name` AS customer, `pr`.`published`
FROM `#__virtuemart_rating_reviews` AS `pr`
LEFT JOIN `#__users` AS `u` ON `pr`.`created_by` = `u`.`id`
LEFT JOIN `#__virtuemart_rating_votes` AS `rv` on `rv`.`virtuemart_product_id`=`pr`.`virtuemart_product_id` and `rv`.`created_by`=`u`.`id`
WHERE  `p`.`virtuemart_product_id` in ('. explode(',',$ids).')
ORDER BY `pr`.`created_by`';
$db->setQuery($q);
$reviews = $db->loadOnjectList();
return $reviews;
}


calling it with getAllReviews($prodcut_id)
This need only 3 queries + 1 for each child level
If you check before for the product_parent_id, for a standard product, this don't get any overhead.

The function was wrote from scratch and not tested.(only the 2 first queries was tested)

P.S: the time to get all reviews with 2 levels childs is +/- 150 ms for the 2 first queries and 50 to 100 ms in the last query to get the reviews
Update: removed user info and only getting the name, now the last query speed is 20ms to 50 ms

bart198x

I will play with that but don't know if even in this weak :(, maybe... (got to do different things).
The database solutions is of course better way (non stop loaded model in loop makes some wrong things with $product :) ), and that way must be taken.
As I say maybe I play with that soon...

Just one question more. Building plugin for that case only - because average rating is not needed to be loaded every time child-parent is choosen (actually just once - on product page load), how to trigger that to prevent overloading by AJAX?? hmm.
I will try to write my first vm plugin :) but to be honest that is one of my first scripts - 5th or 6th maybe, besides some small modifications.
I read about plugin system quite a bit but never try it actually so I don't know what will happen with that :)

lindapowers

Quote from: bart198x on May 06, 2015, 16:48:20 PM
I will play with that but don't know if even in this weak :(, maybe... (got to do different things).
The database solutions is of course better way (non stop loaded model in loop makes some wrong things with $product :) ), and that way must be taken.
As I say maybe I play with that soon...

Just one question more. Building plugin for that case only - because average rating is not needed to be loaded every time child-parent is choosen (actually just once - on product page load), how to trigger that to prevent overloading by AJAX?? hmm.
I will try to write my first vm plugin :) but to be honest that is one of my first scripts - 5th or 6th maybe, besides some small modifications.
I read about plugin system quite a bit but never try it actually so I don't know what will happen with that :)

Did you ever manage to build a plugin for this purpose?

Studio 42

I think, a setting in Vm to use only parent_id in core for reviews, is a solution. Because this depend really how you use your shop product childs.
Advantage of using reviews in parent, is that you can calculate the note always from same product.
Because child inherite all from parent, i think that reviews can use the same logic.

Another solution is ot set parent_id in the review form at the place of child id.