В решении Аспро Максимум для 1С-Битрикс отзывы к товарам могут быть реализованы через комментарии блога. В большинстве случаев этот механизм работает корректно, но иногда в карточке товара появляется странное расхождение: во вкладке «Отзывы» отображается одно количество комментариев, а рядом со звёздами рейтинга в верхней части карточки — другое количество отзывов.
Например, во вкладке товара видно 8 опубликованных отзывов, каждый из которых имеет оценку 5 звёзд, а в верхней части карточки отображается только 7 отзывов. При этом модерация отключена, комментарии действительно опубликованы, а кеш сайта не является причиной проблемы.
Какие свойства участвуют в подсчёте отзывов
В карточке товара Аспро Максимум могут использоваться несколько разных свойств, связанных с отзывами и комментариями:
- BLOG_COMMENTS_CNT — количество комментариев блога;
- EXTENDED_REVIEWS_COUNT — количество расширенных отзывов;
- EXTENDED_REVIEWS_RAITING — средний рейтинг отзывов.
Важно понимать, что это не одно и то же. Счётчик комментариев блога обновляется стандартной логикой Битрикса, а расширенные отзывы и рейтинг пересчитываются отдельной логикой шаблона Аспро Максимум.
Как проявляется проблема
На странице товара можно увидеть два разных числа. Во вкладке «Отзывы» счётчик может быть связан с количеством комментариев блога:
<span class="element-count muted font_xs rounded3">8</span>А в верхней части карточки товара рядом со звёздами может выводиться значение свойства EXTENDED_REVIEWS_COUNT:
<span class="font_sxs">7</span>В результате пользователь видит 8 отзывов во вкладке, но только 7 отзывов рядом с рейтингом. Это выглядит как ошибка отображения, хотя на самом деле причина находится в механизме пересчёта свойств товара.
Почему рейтинг 5.0 / 5 — это не количество отзывов
Отдельно стоит пояснить отображение среднего рейтинга. Если в блоке отзывов выводится значение:
<span class="count">5.0</span>
<span class="maximum_value">/5</span>это означает не «5 отзывов из 5», а среднюю оценку по шкале от 1 до 5.
Например, если у товара 8 отзывов и каждый отзыв имеет оценку 5 звёзд, средняя оценка будет равна 5.0:
(5 + 5 + 5 + 5 + 5 + 5 + 5 + 5) / 8 = 5.0Поэтому значение 5.0 / 5 является корректным. Количество отзывов должно выводиться отдельно.
В чём была реальная причина
Причина расхождения оказалась в том, что несколько товаров использовали один и тот же BLOG_POST_ID.
Такое может быть нормальной ситуацией. Например, в магазине есть основной товар и уценённая версия этого же товара. Логично, что отзывы у них общие, потому что фактически речь идёт об одной модели.
Но штатный механизм Аспро Максимум при пересчёте расширенных отзывов ищет товар по свойству BLOG_POST_ID и обновляет только один найденный элемент. Если товаров с таким BLOG_POST_ID несколько, то один товар получает актуальные значения, а остальные могут остаться со старыми значениями свойств.
В результате возникает ситуация:
BLOG_COMMENTS_CNT = 8
EXTENDED_REVIEWS_COUNT = 7При этом в базе все 8 комментариев могут быть корректными: опубликованными, корневыми, с нужным POST_ID и заполненной оценкой.
Как проверить наличие нескольких товаров с одним BLOG_POST_ID
Для диагностики можно найти все товары, у которых используется один и тот же BLOG_POST_ID. Например, если ID поста отзывов равен 286, можно выполнить SQL-запрос:
SELECT
BE.ID,
BE.NAME,
BE.ACTIVE,
PROP.VALUE AS BLOG_POST_ID
FROM b_iblock_element BE
INNER JOIN b_iblock_element_property PROP ON PROP.IBLOCK_ELEMENT_ID = BE.ID
WHERE PROP.VALUE = 286;Если запрос возвращает несколько товаров, значит штатный пересчёт Аспро может обновлять только один из них. Именно из-за этого у связанных карточек товара могут отличаться значения EXTENDED_REVIEWS_COUNT и BLOG_COMMENTS_CNT.
Почему лучше не править модуль Аспро напрямую
Технически можно изменить логику внутри модуля Аспро Максимум, чтобы он обновлял все товары с одним BLOG_POST_ID. Но это плохой вариант: при обновлении решения такие изменения могут быть потеряны.
Более безопасный способ — добавить собственный обработчик в init.php. Такой обработчик будет работать поверх штатной логики и не затрагивать файлы модуля.
Решение: пересчитывать отзывы для всех товаров с одним BLOG_POST_ID
Задача обработчика:
- срабатывать при добавлении, обновлении и удалении комментария;
- получать POST_ID комментария;
- считать количество опубликованных корневых комментариев;
- считать средний рейтинг по полю UF_ASPRO_COM_RATING;
- находить все товары с этим же BLOG_POST_ID;
- обновлять у всех найденных товаров свойства отзывов и рейтинга.
Код можно добавить в файл:
/local/php_interface/init.phpЕсли проект использует старую структуру, файл может находиться здесь:
/bitrix/php_interface/init.phpКод обработчика
<?php
// Синхронизация отзывов для всех товаров с одним BLOG_POST_ID.
// Актуально для случаев, когда основной товар и уценка используют общий пост отзывов.
AddEventHandler('blog', 'OnCommentAdd', 'customAsproReviewsSyncOnCommentAddUpdate', 1000);
AddEventHandler('blog', 'OnCommentUpdate', 'customAsproReviewsSyncOnCommentAddUpdate', 1000);
AddEventHandler('blog', 'OnBeforeCommentDelete', 'customAsproReviewsRememberPostIdBeforeDelete', 1000);
AddEventHandler('blog', 'OnCommentDelete', 'customAsproReviewsSyncOnCommentDelete', 1000);
function customAsproReviewsSyncOnCommentAddUpdate($commentID, &$arFields = null)
{
$commentID = (int)$commentID;
if ($commentID <= 0) {
return;
}
$postID = 0;
if (is_array($arFields) && !empty($arFields['POST_ID'])) {
$postID = (int)$arFields['POST_ID'];
}
if ($postID <= 0 && CModule::IncludeModule('blog')) {
$comment = CBlogComment::GetByID($commentID);
if (is_array($comment) && !empty($comment['POST_ID'])) {
$postID = (int)$comment['POST_ID'];
}
}
if ($postID > 0) {
customAsproReviewsScheduleRecalcByPostId($postID);
}
}
function customAsproReviewsRememberPostIdBeforeDelete($commentID)
{
$commentID = (int)$commentID;
if ($commentID <= 0) {
return;
}
if (!CModule::IncludeModule('blog')) {
return;
}
$comment = CBlogComment::GetByID($commentID);
if (is_array($comment) && !empty($comment['POST_ID'])) {
$GLOBALS['CUSTOM_ASPRO_REVIEWS_DELETED_POST_IDS'][$commentID] = (int)$comment['POST_ID'];
}
}
function customAsproReviewsSyncOnCommentDelete($commentID)
{
$commentID = (int)$commentID;
if ($commentID <= 0) {
return;
}
$postID = 0;
if (!empty($GLOBALS['CUSTOM_ASPRO_REVIEWS_DELETED_POST_IDS'][$commentID])) {
$postID = (int)$GLOBALS['CUSTOM_ASPRO_REVIEWS_DELETED_POST_IDS'][$commentID];
}
if ($postID > 0) {
customAsproReviewsScheduleRecalcByPostId($postID);
}
}
function customAsproReviewsScheduleRecalcByPostId($postID)
{
static $scheduled = array();
$postID = (int)$postID;
if ($postID <= 0 || isset($scheduled[$postID])) {
return;
}
$scheduled[$postID] = true;
register_shutdown_function('customAsproReviewsRecalcByPostId', $postID);
}
function customAsproReviewsRecalcByPostId($postID)
{
$postID = (int)$postID;
if ($postID <= 0) {
return;
}
if (!CModule::IncludeModule('blog') || !CModule::IncludeModule('iblock')) {
return;
}
$commentsCount = 0;
$commentsRatingSum = 0;
$commentsRatingCount = 0;
$resComments = CBlogComment::GetList(
array('ID' => 'ASC'),
array(
'POST_ID' => $postID,
'PARENT_ID' => false,
'PUBLISH_STATUS' => 'P',
),
false,
false,
array(
'ID',
'UF_ASPRO_COM_RATING',
)
);
while ($comment = $resComments->Fetch()) {
$commentsCount++;
$rating = (int)$comment['UF_ASPRO_COM_RATING'];
if ($rating > 0) {
$commentsRatingCount++;
$commentsRatingSum += $rating;
}
}
$ratingValue = 0;
if ($commentsRatingCount > 0) {
$ratingValue = round($commentsRatingSum / $commentsRatingCount, 1);
}
$resElements = CIBlockElement::GetList(
array('ID' => 'ASC'),
array(
'PROPERTY_BLOG_POST_ID' => $postID,
),
false,
false,
array(
'ID',
'IBLOCK_ID',
'NAME',
'PROPERTY_BLOG_POST_ID',
)
);
while ($element = $resElements->Fetch()) {
CIBlockElement::SetPropertyValuesEx(
(int)$element['ID'],
(int)$element['IBLOCK_ID'],
array(
'BLOG_COMMENTS_CNT' => $commentsCount,
'EXTENDED_REVIEWS_COUNT' => $commentsCount,
'EXTENDED_REVIEWS_RAITING' => $ratingValue,
)
);
}
}Почему отдельно обработано удаление отзывов
При добавлении или обновлении комментария его POST_ID можно получить напрямую. Но при удалении комментария после события удаления запись уже может отсутствовать в базе.
Поэтому используется связка из двух событий:
- OnBeforeCommentDelete — до удаления запоминается POST_ID комментария;
- OnCommentDelete — после удаления выполняется пересчёт отзывов по сохранённому POST_ID.
Без такой обработки при удалении отзыва счётчики могли бы не уменьшаться.
Дополнительная доработка: сортировка отзывов по новизне
Ещё одна распространённая особенность Аспро Максимум — сортировка отзывов по умолчанию. Часто первым выбранным вариантом является «Сначала позитивные». Если у большинства отзывов одинаковая оценка, порядок отзывов может выглядеть нелогично.
Например, старый отзыв с 5 звёздами может отображаться выше нового отзыва с такой же оценкой. Чтобы пользователю было проще ориентироваться, можно сделать сортировку по умолчанию «Сначала новые».
Файл шаблона сортировки:
/bitrix/templates/aspro_max/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/sort.phpПример порядка сортировок:
<?php
$arAvailableSort = array(
array(
'PROP' => 'DateFormated',
'ORDER' => 'SORT_DESC',
'MESSAGE' => 'DATE_DESC',
),
array(
'PROP' => 'UF_ASPRO_COM_RATING',
'ORDER' => 'SORT_DESC',
'MESSAGE' => 'RATING_DESC',
),
array(
'PROP' => 'UF_ASPRO_COM_RATING',
'ORDER' => 'SORT_ASC',
'MESSAGE' => 'RATING_ASC',
),
array(
'PROP' => 'DateFormated',
'ORDER' => 'SORT_ASC',
'MESSAGE' => 'DATE_ASC',
),
array(
'PROP' => 'UF_ASPRO_COM_LIKE',
'ORDER' => 'SORT_DESC',
'MESSAGE' => 'LIKE_DESC',
),
);Также нужно убедиться, что при первой загрузке страницы фактическая сортировка совпадает с выбранным пунктом. Для этого в result_modifier.php задаётся дефолтная сортировка по дате.
Файл:
/bitrix/templates/aspro_max/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/result_modifier.php <?php
global $USER_FIELD_MANAGER;
$arParams['SORT_PROP'] = !empty($_SESSION['REVIEW_SORT_PROP'])
? $_SESSION['REVIEW_SORT_PROP']
: 'DateFormated';
$arParams['SORT_ORDER'] = !empty($_SESSION['REVIEW_SORT_ORDER'])
? $_SESSION['REVIEW_SORT_ORDER']
: 'SORT_DESC';Дополнительная доработка: вывод года в дате отзыва
В некоторых шаблонах Аспро Максимум дата комментария выводится без года. Это неудобно, если отзывы накапливались в течение нескольких лет: пользователь видит день и месяц, но не понимает, к какому году относится отзыв.
Файл шаблона:
/bitrix/templates/aspro_max/components/bitrix/catalog.comments/catalog/bitrix/blog.post.comment/adapt/template.phpБыло:
<div class="blog-comment-date"><?=FormatDate('d F, H:i', MakeTimeStamp($comment["DateFormated"]))?></div>Стало:
<div class="blog-comment-date"><?=FormatDate('d.m.Y, H:i', MakeTimeStamp($comment["DateFormated"]))?></div> 
Что получилось в результате
После доработки количество комментариев, количество расширенных отзывов и средний рейтинг стали синхронно обновляться у всех товаров, связанных с одним BLOG_POST_ID.
Теперь при добавлении, изменении или удалении отзыва обновляются следующие свойства:
- BLOG_COMMENTS_CNT;
- EXTENDED_REVIEWS_COUNT;
- EXTENDED_REVIEWS_RAITING.
Это позволяет корректно использовать общие отзывы для нескольких карточек товара, например для основного товара и уценённой позиции.
Если в Аспро Максимум не совпадает количество отзывов и комментариев в карточке товара, сначала стоит проверить не кеш, а связь товаров с BLOG_POST_ID.
Если несколько товаров используют один и тот же пост отзывов, штатный механизм может обновлять только один товар. В таком случае лучше добавить собственный обработчик в init.php, который будет пересчитывать отзывы и рейтинг для всех связанных товаров.
Такой подход не требует изменения файлов модуля Аспро Максимум и поэтому безопаснее с точки зрения будущих обновлений решения.



