[spoiler]
Для начала расскажу, что такое рейтинг и как он вычисляется.
Рейтинг это оценка, состоящая из множества критериев, например, таких как «голосование за сообщение пользователя» или «активность на форуме».
Каждый критерий, рассчитывая свои результаты, использует собственную логику. Благодаря такому подходу возможно реализовать любые формулы.
Критерии рейтинга и формулы
Когда результаты критериев рассчитаны, подсчитывается итоговая оценка.
Она может быть либо суммой критериев, либо их средним значением.
Способ подсчёта
Теперь перейдем к тому, как создать свой критерий рейтингования.
Для примера создадим новый критерий «Голосование за пользователя».
Посмотреть примеры других критериев вы можете в модуле forum и blog.
Создание критерия состоит из трех этапов:
1. Создать класс и зарегистрировать события;
2. Настроить критерии и написать функции расчета;
3. Добавить компоненты рейтингового голосования в нужных частях кода;
Создаем класс и регистрируем события.
Создайте файл ratings_components.php в папке классов вашего модуля (структура и содержание файла будут рассмотрены ниже)
Создание файла критериев рейтинга
В корне вашего модуля, в файле include.php необходимо подключить этот класс.
CModule::AddAutoloadClasses( "test", array( "CRatingsComponentsTest" => "classes/general/ratings_components.php", ) ); |
Необходимо зарегистрировать обработчики событий.
RegisterModuleDependences("main", "OnAfterAddRating", "test", "CRatingsComponentsTest", "OnAfterAddRating"); RegisterModuleDependences("main", "OnAfterUpdateRating", "test", "CRatingsComponentsTest", "OnAfterUpdateRating"); RegisterModuleDependences("main", "OnSetRatingsConfigs", "test", "CRatingsComponentsTest", "OnSetRatingConfigs"); RegisterModuleDependences("main", "OnGetRatingsConfigs", "test", "CRatingsComponentsTest", "OnGetRatingConfigs"); RegisterModuleDependences("main", "OnGetRatingsObjects", "test", "CRatingsComponentsTest", "OnGetRatingObject"); |
Настраиваем критерии и пишем функции расчета
У файла ratings_components.php следующая структура
<? IncludeModuleLangFile(__FILE__); class CRatingsComponentsTest { // функция содержит настройки критериев рейтингования function OnGetRatingConfigs() {} // функция отдает доступные объекты для рейтингования function OnGetRatingObject() {} // Функция события, срабатывает при добавлении нового рейтинга function OnAfterAddRating($ID, $arFields) {} // Функция события, срабатывает при редактировании нового рейтинга function OnAfterUpdateRating($ID, $arFields) {} // Проверка введенных пользователем данных (полей указанных в конфигурационном массиве) при сохранении и редактировании рейтинга function __CheckFields($entityId, $arConfigs) {} // Вспомогательная функция, которая собирает значения по умолчанию для полей указанных в конфигурационном массиве function __AssembleConfigDefault($objectType = null) {} } ?> |
Рассмотрим эти функции детально.
function OnGetRatingConfigs
Функция содержит массив с настройками критериев рейтингования
function OnGetRatingConfigs() { $arConfigs = array( 'MODULE_ID' => 'TEST', 'MODULE_NAME' => 'Тестовый модуль', ); $arConfigs["COMPONENT"]["USER"]["VOTE"][] = array( "ID" => 'USER_VOTE', "REFRESH_TIME" => '3600', "CLASS" => 'CRatingsComponentsTest', "CALC_METHOD" => 'CalcUserVote', "NAME" => 'Голосование за пользователя', "FIELDS" => array( array( "ID" => 'COEFFICIENT', "DEFAULT" => '1', ), ) ); return $arConfigs; } |
Подробнее, разберем составляющие конфигурационного массива:
$arConfigs = array( 'MODULE_ID' => 'TEST', 'MODULE_NAME' => 'Тестовый модуль', ); |
MODULE_NAME – имя модуля (выводится в названии вкладки), можно не указывать если такой модуль уже был обьявлен ранее.
$arConfigs["COMPONENT"]["USER"]["VOTE"][] |
VOTE – это тип рейтингового голосования, может быть либо VOTE либо RATING
$arConfigs["COMPONENT"]["USER"]["VOTE"][] = array( "ID" => 'USER_VOTE', "REFRESH_TIME" => '3600', "CLASS" => 'CRatingsComponentsTest', "CALC_METHOD" => 'CalcUserVote', "NAME" => 'Голосование за пользователя', "FIELDS" => array( array( "ID" => 'COEFFICIENT', "DEFAULT" => '1', ), ) ); |
ID – идентификатор критерия
REFRESH_TIME – через какое время происходит перерасчет критерия (в минутах)
CLASS – имя класса в котором есть функция подсчета критерия
CALC_METHOD – имя функции подсчета критерия
NAME – название критерия
FIELDS – поля вводимых данных для модификации итогов расчета
Необязательные поля
DESC – Описание критерия (текст)
FORMULA – Формула подсчета (текст)
FORMULA_DESC – Описание формулы (текст)
EXCEPTION_METHOD – функция отключения критерия. Функция должна возвращать false если критерий отключать не нужно, либо Текст если критерий необходимо отключить (текст будет выведен над критериями рейтингования).
Например критерий «Актиновсть пользователей на форуме» имеет такое исключение, если на форуме нет необходимых индексов рейтинг учитывать данный критерий не будет.
Внутри FIELDS может быть несколько полей, для этого нужно указать несколько массивов.
array( "ID" => 'COEFFICIENT', "DEFAULT" => '1', ), |
DEFAULT – значение по умолчанию
Необязательные поля
NAME – название поля
Функция подсчета критерия
Выполняет подсчет по заданной логике и добавляет результаты в таблицу
function CalcUserVote($arConfigs) { global $DB; $err_mess = (CRatings::err_mess())."<br>Function: CalcUserVote<br>Line: "; CRatings::AddComponentResults($arConfigs); $strSql = "DELETE FROM b_rating_component_results WHERE RATING_ID = '".IntVal($arConfigs['RATING_ID'])."' AND COMPLEX_NAME = '".$DB->ForSql($arConfigs['COMPLEX_NAME'])."'"; $res = $DB->Query($strSql, false, $err_mess.__LINE__); $strSql = "INSERT INTO b_rating_component_results (RATING_ID, MODULE_ID, RATING_TYPE, NAME, COMPLEX_NAME, ENTITY_ID, ENTITY_TYPE_ID, CURRENT_VALUE) SELECT '".IntVal($arConfigs['RATING_ID'])."' RATING_ID, '".$DB->ForSql($arConfigs['MODULE_ID'])."' MODULE_ID, '".$DB->ForSql($arConfigs['RATING_TYPE'])."' RATING_TYPE, '".$DB->ForSql($arConfigs['NAME'])."' NAME, '".$DB->ForSql($arConfigs['COMPLEX_NAME'])."' COMPLEX_NAME, RV.ENTITY_ID ENTITY_ID, '".$DB->ForSql($arConfigs['ENTITY_ID'])."' ENTITY_TYPE_ID, SUM(RV.TOTAL_VALUE)*".$arConfigs['CONFIG']['COEFFICIENT']." CURRENT_VALUE FROM b_rating_voting RV WHERE RV.ENTITY_TYPE_ID = 'USER_VOTE' AND RV.ENTITY_ID > 0 GROUP BY ENTITY_ID"; $res = $DB->Query($strSql, false, $err_mess.__LINE__); return true; } |
В функцию подается массив $arConfigs, в котором есть данные о рейтинге и данные о настройках критерия.
Перед подсчетом необходимо выполнить функцию CRatings::AddComponentResults($arConfigs); и удалить все старые расчеты.
Дальше вы пишите логику подсчета, результат добавляете в таблицу b_rating_component_results
RATING_ID – идентификатор рейтинга.
MODULE_ID – идентификатор модуля, для которого идет посчет.
RATING_TYPE– тип голосования.
NAME – идентификатор критерия.
COMPLEX_NAME – комплексный идентификатор критерия.
ENTITY_TYPE_ID – Объект оценки.
ENTITY_ID – идентификатор объекта оценки, в нашем случае это ID пользователя.
CURRENT_VALUE – итог подсчета критерия к конкретному ENTITY_ID, в нашем случае это сумма всех голосов за пользователя умноженная на коэффициент указанный в настройке рейтинга.
При добавлении результатов, настоятельно рекомендую использовать конструкцию INSERT…SELECT для минимизации нагрузки на проект при пересчете рейтинга. На примере подсчета активности на форуме (с большим кол-вом записей), использование такой конструкции позволило сократить время подсчета с 30 до 0.6 секунды!
function OnGetRatingObject
Функция отдает доступные обьекты для рейтингования
function OnGetRatingObject() { $arRatingConfigs = CRatingsComponentsTest::OnGetRatingConfigs(); foreach ($arRatingConfigs["COMPONENT"] as $SupportType => $value) $arSupportType[] = $SupportType; return $arSupportType; } |
function OnAfterAddRating
function OnAfterUpdateRating
В этих функциях вызывается проверка введенных пользователем данных (полей указанных в конфигурационном массиве), срабатывает при добавлении и редактировании рейтинга.
function OnAfterAddRating($ID, $arFields) { $arFields['CONFIGS']['TEST'] = CRatingsComponentsTest::__CheckFields($arFields['ENTITY_ID'], $arFields['CONFIGS']['TEST']); return $arFields; } function OnAfterUpdateRating($ID, $arFields) { $arFields['CONFIGS']['TEST'] = CRatingsComponentsTest::__CheckFields($arFields['ENTITY_ID'], $arFields['CONFIGS']['TEST']); return $arFields; } |
function __CheckFields
Проверка введенных пользователем данных (полей указанных в конфигурационном массиве) при сохранении или редактировании рейтинга.
function __CheckFields($entityId, $arConfigs) { $arDefaultConfig = CRatingsComponentsTest::__AssembleConfigDefault($entityId); if ($entityId == "USER") { if (isset($arConfigs['VOTE']['USER_VOTE'])) if (!preg_match('/^\d{1,7}\.?\d{0,4}$/', $arConfigs['VOTE']['USER_VOTE']['COEFFICIENT'])) $arConfigs['VOTE']['USER_VOTE']['COEFFICIENT'] = $arDefaultConfig['VOTE']['USER_VOTE']['COEFFICIENT']['DEFAULT']; } return $arConfigs; } |
function __AssembleConfigDefault
Вспомогательная функция, которая собирает значения по умолчанию для полей указанных конфигурационном массиве.
function __AssembleConfigDefault($objectType = null) { $arConfigs = array(); $arRatingConfigs = CRatingsComponentsTest::OnGetRatingConfigs(); if (is_null($objectType)) { foreach ($arRatingConfigs["COMPONENT"] as $OBJ_TYPE => $TYPE_VALUE) foreach ($TYPE_VALUE as $RAT_TYPE => $RAT_VALUE) foreach ($RAT_VALUE as $VALUE_CONFIG) foreach ($VALUE_CONFIG['FIELDS'] as $VALUE_FIELDS) $arConfigs[$OBJ_TYPE][$RAT_TYPE][$VALUE_CONFIG['ID']][$VALUE_FIELDS['ID']]['DEFAULT'] = $VALUE_FIELDS['DEFAULT']; } else { foreach ($arRatingConfigs["COMPONENT"][$objectType] as $RAT_TYPE => $RAT_VALUE) foreach ($RAT_VALUE as $VALUE_CONFIG) foreach ($VALUE_CONFIG['FIELDS'] as $VALUE_FIELDS) $arConfigs[$RAT_TYPE][$VALUE_CONFIG['ID']][$VALUE_FIELDS['ID']]['DEFAULT'] = $VALUE_FIELDS['DEFAULT']; } return $arConfigs; } |
Добавляем компонент «рейтинговое голосование» (bitrix:rating.vote)
Во всех местах где вы хотите вывести рейтинговое голосование необходимо добавить вызов компонента bitrix:rating.vote.
Компонент bitrix:rating.vote работает в двух режимах:
1. Автоматически получает данные - необходимо использовать при единичном вызове, например в карточке пользователя.
2. В компонент нужно подавать данные – необходимо использовать при множественном вызове, например в сообщениях форума.
В качестве примера, укажу какой код необходимо добавить в свой шаблон комплексного компонента forum (основанный на шаблоне .default).
Открываем файл forum\templates\(ваш шаблон)\bitrix\forum.topic.read\(ваш шаблон)\template_message.php
Находим строчку:
$arRatingResult = CRatings::GetRatingResult($arParams["RATING_ID"], array_unique($arAuthorId)); |
$arRatingVote['USER_VOTE'] = CRatings::GetRatingVoteResult('USER_VOTE', array_unique($arAuthorId)); |
Находим код:
<span> <? $GLOBALS["APPLICATION"]->IncludeComponent( "bitrix:rating.result", "", Array( "RATING_ID" => $arParams["RATING_ID"], "ENTITY_ID" => $arRatingResult[$res['AUTHOR_ID']]['ENTITY_ID'], "CURRENT_VALUE" => $arRatingResult[$res['AUTHOR_ID']]['CURRENT_VALUE'], "PREVIOUS_VALUE" => $arRatingResult[$res['AUTHOR_ID']]['PREVIOUS_VALUE'], ), null, array("HIDE_ICONS" => "Y") ); ?> </span> |
<span>Голосовать: <? $voteEntityId = $res['AUTHOR_ID']; $GLOBALS["APPLICATION"]->IncludeComponent( "bitrix:rating.vote", "", Array( "ENTITY_TYPE_ID" => 'USER_VOTE', "ENTITY_ID" => $voteEntityId, "OWNER_ID" => $res['AUTHOR_ID'], "USER_HAS_VOTED" => $arRatingVote['USER_VOTE'][$voteEntityId]['USER_HAS_VOTED'], "TOTAL_VOTES" => $arRatingVote['USER_VOTE'][$voteEntityId]['TOTAL_VOTES'], "TOTAL_POSITIVE_VOTES" => $arRatingVote['USER_VOTE'][$voteEntityId]['TOTAL_POSITIVE_VOTES'], "TOTAL_NEGATIVE_VOTES" => $arRatingVote['USER_VOTE'][$voteEntityId]['TOTAL_NEGATIVE_VOTES'], "TOTAL_VALUE" => $arRatingVote['USER_VOTE'][$voteEntityId]['TOTAL_VALUE'] ), null, array("HIDE_ICONS" => "Y") );?> </span> |
После добавления этого кода, под рейтингом пользователя появятся кнопки для голосования.
Рейтинговое голосование в сообщении форума
Что бы ваш критерий начал учитываться в рейтинге, необходимо зайти в уже созданный рейтинг (или создать новый) и активировать критерий.
Активация критерия рейтингования
Позже, у себя в блоге, я размещу «Road map» рейтингов. Опишу то, что есть уже сейчас и к чему мы стремимся в будущем.
P.S.: все непонятные моменты укажите в комментариях, распишу более подробно.
Загрузить текстовый модуль с готовыми примером, можно
Я видел Агент, в списке агентов, то он ставится не переодический, значит запускается только 1 раз при создании рейтинга.
Где то в коде еще зашито обновление рейтинга? Или лучше это делать кроном? Либо сделать агент переодическим?
Расчет рейтингов происходит в два этапа:
1. Подсчет результатов критериев на основании своей логики;
2. Подсчет результатов рейтинга на основании результатов подсчета критериев;
Перерасчет результатов критериев происходит на основании значения REFRESH_TIME (с округлением до часа в большую сторону).
Например если вы выставите REFRESH_TIME=3600 - критерий будет рассчитываться каждый час, если вы выставите REFRESH_TIME=7000 - каждый второй час.
Подсчет результата рейтинга происходит каждый час, на основании уже подсчитанных результатов «критериев рейтингования». Если какой то критерий в данный запуск не подсчитывался, данные берутся из его последнего расчета.
И ещё один, момент:
При создании, редактировании и пересчете рейтинга из админки - все критерии рейтинга принудительно пересчитываются, сделано это для того что бы была возможность увидеть результат моментально.
Посмотрите пример "Agent007" - агент выполняется до тех пор, пока мы возвращаем функцию вызова, как только мы вернем пустоту, агент удалится.
В случае с рейтингами, мы всегда возвращаем вызываемую функцию.
С датой в прошлом (01.11.2010 00:00:00, а сейчас 01.11.2010 23:06:54), без даты последнего запуска и не переодический.
Значит он вообще не выполнялся и не собирается
Думаю где то в тексте не хватает еще пункта, что надо зайти в рейтинги в Админке и настроить.
Этот компонент может присутсвовать как в посчете "рейтинга" пользователя, так и в подсчете "рейтинга" темы, ведь сообщение в равной степени пренадлежит и пользователю и теме, тоже самое с темой сообщения.
P.S. На очень скорую руку набросал код для выводя ТОПА пользователей на основании рейтинга. Код сырой, но рабочий:
Для вывода топа пользователей есть более простой способ, посмотреть как он работает можно накинув компонент bitrix:socialnetwork.user_search
Вот пример выборки топа, пример отсортирует по убыванию рейтинга.
$ratingId - идентификатор рейтинга;
$dbUsers - CDBResult с данными пользователей, значение рейтинга хранится в поле "RATING_".$ratingId;
P.S. Функцию CRatings::GetRatingResult используете крайне не эффективно, запросов на выборку будет очень много.
Такой способ подойдет к единичной выборке, но не к группе.
В вашем случае необходимо вызвать функцию так:
CRatings::GetRatingResult($ratingId, $arUserId);
$ratingId - идентификатор рейтинга (число);
$arUserId - массив идентификаторов пользователей (массив);
Скоро будет документация, будет проще.
Вот её и используем для получения сортированного списка, а то с модулями Битрикс дело обстоит слишком сложно, как для такой простой задачи.
Вообще в системе должен быть отдельный компонент вывода рейтинга и уже давно, ведь Битрикс - готовое решение из коробки.
Можно ли сделать, чтобы гости тоже могли голосовать?
Видимо с текстом напутали, поправим, спасибо!
Подскажите, как будут делаться выборки результатов рейтинга? Я так понял, что для пользователя можно выбирать поля RATING_* в функции GetList, а в GetByID тоже они будут присутствовать?
Как можно выбрать результаты для сообщений, комментариев? Будет ли выборка значений рейтинга встроена в функции получения данных по блогам, форумам и т.п.?
Результаты рейтинговых голосований можно получить функцией CRatings::GetRatingVoteResult($entityTypeId, $entityId)
$entityTypeId - идентификатор сущности голосования (FORUM_TOPIC для тем форума, FORUM_POST для сообщений форума, BLOG_POST для постов в блоге, BLOG_COMMENT для комментариев в блоге)
$arUserId - массив идентификаторов сущностей (например список идентификаторов тем форума или список идентификаторов комментариев блога)
(пример использования так же есть выше)
Добавление выборки значений рейтинга (и значений рейтингового голосования) в функции получения данных по блогам, форумам, пользователям (кроме функции CUser::GetList) - не планируется.
Для каждого рейтинга создается агент, когда время его расчета подходит он обновляет критерии и суммирует результаты (для голосований раз в час, для активности раз в два часа) - делается это все очень быстро.
Пользователи могут голосовать за тему в блоге группы, при этом голос проставляется как самой теме в блоге, так и создателю темы. В итоге проголосовав за тему, автоматически добавляется голос и создателю темы.Как это проще всего сделать?
Отдельное голосование за тему и создателя темы работает.
Час убил, не понимая почему не работает, оказывается с AddEventHandler не срабатывает то есть нужно именно RegisterModuleDependences делать, соответственно уже точно нужен свой модуль. Жень, зачем такое ограничение? Как-то хитро события дергаются?
+
стал разбираться, плюнул, сделал по-своему
создал таблицу и компонент к ней