Есть следующий кейс:
Компонент news.list выводит элементы из инфоблока. У элементов инфоблока есть множественное свойство типа "Привязка к пользователю" под названием "Пользователи, которым понравилась новость". Задача: выводить логины пользователей, которым понравилась новость. Если пользователь авторизован, и он поставил "лайк", то выводить кнопку "Не нравится", если он не ставил "лайк", то выводить кнопку "Нравится". Ну т.е. типичный функционал оценки контента.
Мне пришло в голову несколько вариантов того, как можно организовать этот функционал в рамках штатного news.list и увязать его работу с управляемым кэшем компонента, некоторые варианты мне показались вполне приемлемыми, некоторые - не очень.
1) Добавить к news.list параметр "USER_ID" и скармливать ему id авторизованного пользователя. А в template уже работаем с $arParams["USER_ID"] . Серьёзный недостаток в том, что управляемый кэш зависит от id пользователя, что является очень нежелательной ситуацией, ибо для каждого авторизованного пользователя будет создаваться свой экземпляр кэша. Вес файлов кэша будет очень большой.
2) Добавить к news.list параметр "LIKED_NEWS_IDS" , который будет содержать id статей, лайкнутых авторизованным пользователем. Т.к. возможен такой вариант, когда несколько разных пользователей полайкали один и тот-же набор статей, то объём кэша должен быть меньше (ну по крайней мере не больше), чем в реализации, описанной в первом варианте. Вариант считаю неидеальным, зависимость управляемого кэша от id пользователя всё-равно присутствует, но на этот раз косвенная.
Нутром чую, что можно сделать как-то проще, но пока остановился на этом варианте.
Вот код:
news.php
<?
global $USER;
if ($USER->IsAuthorized()) {
$arLikedNewsIDs = HelperIblock::getLikedNews($USER->GetID(), $arParams["IBLOCK_ID"]);
}else{
$arLikedNewsIDs = array();
}
?>
<?$APPLICATION->IncludeComponent(
"bitrix:news.list",
"",
Array(
"IBLOCK_TYPE" => $arParams["IBLOCK_TYPE"],
"IBLOCK_ID" => $arParams["IBLOCK_ID"],
"NEWS_COUNT" => $arParams["NEWS_COUNT"],
"LIKED_NEWS_IDS" => $arLikedNewsIDs, // id-шники новостей, лайкнутые авторизованным пользователем
"SORT_BY1" => $arParams["SORT_BY1"],
"SORT_ORDER1" => $arParams["SORT_ORDER1"],
"SORT_BY2" => $arParams["SORT_BY2"],
"SORT_ORDER2" => $arParams["SORT_ORDER2"],
"FILTER_NAME" => $arParams["FILTER_NAME"],
"FIELD_CODE" => $arParams["LIST_FIELD_CODE"],
"PROPERTY_CODE" => $arParams["LIST_PROPERTY_CODE"],
"CHECK_DATES" => $arParams["CHECK_DATES"],
"IBLOCK_URL" => $arResult["FOLDER"].$arResult["URL_TEMPLATES"]["news"],
"SECTION_URL" => $arResult["FOLDER"].$arResult["URL_TEMPLATES"]["section"],
"DETAIL_URL" => $arResult["FOLDER"].$arResult["URL_TEMPLATES"]["detail"],
"SEARCH_PAGE" => $arResult["FOLDER"].$arResult["URL_TEMPLATES"]["search"],
"CACHE_TYPE" => $arParams["CACHE_TYPE"],
"CACHE_TIME" => $arParams["CACHE_TIME"],
"CACHE_FILTER" => $arParams["CACHE_FILTER"],
"CACHE_GROUPS" => $arParams["CACHE_GROUPS"],
"PREVIEW_TRUNCATE_LEN" => $arParams["PREVIEW_TRUNCATE_LEN"],
"ACTIVE_DATE_FORMAT" => $arParams["LIST_ACTIVE_DATE_FORMAT"],
"SET_TITLE" => $arParams["SET_TITLE"],
"SET_BROWSER_TITLE" => "Y",
"SET_META_KEYWORDS" => "Y",
"SET_META_DESCRIPTION" => "Y",
"MESSAGE_404" => $arParams["MESSAGE_404"],
"SET_STATUS_404" => $arParams["SET_STATUS_404"],
"SHOW_404" => $arParams["SHOW_404"],
"FILE_404" => $arParams["FILE_404"],
"SET_LAST_MODIFIED" => $arParams["SET_LAST_MODIFIED"],
"INCLUDE_IBLOCK_INTO_CHAIN" => $arParams["INCLUDE_IBLOCK_INTO_CHAIN"],
"ADD_SECTIONS_CHAIN" => "N",
"HIDE_LINK_WHEN_NO_DETAIL" => $arParams["HIDE_LINK_WHEN_NO_DETAIL"],
"PARENT_SECTION" => "",
"PARENT_SECTION_CODE" => "",
"INCLUDE_SUBSECTIONS" => "Y",
"DISPLAY_DATE" => $arParams["DISPLAY_DATE"],
"DISPLAY_NAME" => "Y",
"DISPLAY_PICTURE" => $arParams["DISPLAY_PICTURE"],
"DISPLAY_PREVIEW_TEXT" => $arParams["DISPLAY_PREVIEW_TEXT"],
"MEDIA_PROPERTY" => $arParams["MEDIA_PROPERTY"],
"SLIDER_PROPERTY" => $arParams["SLIDER_PROPERTY"],
"PAGER_TEMPLATE" => $arParams["PAGER_TEMPLATE"],
"DISPLAY_TOP_PAGER" => $arParams["DISPLAY_TOP_PAGER"],
"DISPLAY_BOTTOM_PAGER" => $arParams["DISPLAY_BOTTOM_PAGER"],
"PAGER_TITLE" => $arParams["PAGER_TITLE"],
"PAGER_SHOW_ALWAYS" => $arParams["PAGER_SHOW_ALWAYS"],
"PAGER_DESC_NUMBERING" => $arParams["PAGER_DESC_NUMBERING"],
"PAGER_DESC_NUMBERING_CACHE_TIME" => $arParams["PAGER_DESC_NUMBERING_CACHE_TIME"],
"PAGER_SHOW_ALL" => $arParams["PAGER_SHOW_ALL"],
"PAGER_BASE_LINK_ENABLE" => $arParams["PAGER_BASE_LINK_ENABLE"],
"PAGER_BASE_LINK" => $arParams["PAGER_BASE_LINK"],
"PAGER_PARAMS_NAME" => $arParams["PAGER_PARAMS_NAME"],
"USE_RATING" => $arParams["USE_RATING"],
"DISPLAY_AS_RATING" => $arParams["DISPLAY_AS_RATING"],
"MAX_VOTE" => $arParams["MAX_VOTE"],
"VOTE_NAMES" => $arParams["VOTE_NAMES"],
"USE_SHARE" => $arParams["LIST_USE_SHARE"],
"SHARE_HIDE" => $arParams["SHARE_HIDE"],
"SHARE_TEMPLATE" => $arParams["SHARE_TEMPLATE"],
"SHARE_HANDLERS" => $arParams["SHARE_HANDLERS"],
"SHARE_SHORTEN_URL_LOGIN" => $arParams["SHARE_SHORTEN_URL_LOGIN"],
"SHARE_SHORTEN_URL_KEY" => $arParams["SHARE_SHORTEN_URL_KEY"],
"TEMPLATE_THEME" => $arParams["TEMPLATE_THEME"],
),
$component
);?> |
Код метода HelperIblock::getLikedNews :
class HelperIblock
{
public static function getLikedNews($userID, $IBlockNewsID){
if (!\Bitrix\Main\Loader::includeModule('iblock'))
return;
$arLikedNewsIDs = array();
$cache = new CPHPCache();
$cache_time = 86400;
$cache_id = 'getLikedNews_' . SITE_ID . "_" . $userID . "_" . $IBlockNewsID;
$cache_path = '/getLikedNews/';
if ($cache_time > 0 && $cache->InitCache($cache_time, $cache_id, $cache_path)) {
$res = $cache->GetVars();
if (is_array($res["arLikedNewsIDs"]) &&
(count($res["arLikedNewsIDs"]) > 0)) {
echo "cached";
$arLikedNewsIDs = $res["arLikedNewsIDs"];
}
}
if (empty($arLikedNewsIDs)) {
$arFilter = array(
"IBLOCK_ID" => $IBlockNewsID,
"PROPERTY_LIKED_USERS" => $userID
);
$rsElement = CIBlockElement::GetList(
array(),
$arFilter,
false,
false,
array("ID", "PROPERTY_LIKED_USERS")
);
global $CACHE_MANAGER;
$CACHE_MANAGER->StartTagCache($cache_path);
while ($arElement = $rsElement->GetNext()) {
$arLikedNewsIDs[] = $arElement["ID"];
$CACHE_MANAGER->RegisterTag("LikedNewID_" . $arElement["ID"]);
}
$CACHE_MANAGER->EndTagCache();
if ($cache_time > 0) {
$cache->StartDataCache($cache_time, $cache_id, $cache_path);
$cache->EndDataCache(array("arLikedNewsIDs" => $arLikedNewsIDs));
}
}
return $arLikedNewsIDs;
}
}
|
template.php :
<div class="news-wrap__list ajax-list">
<?foreach($arResult["ITEMS"] as $arItem):?>
<?
$this->AddEditAction($arItem['ID'], $arItem['EDIT_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_EDIT"));
$this->AddDeleteAction($arItem['ID'], $arItem['DELETE_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_DELETE"), array("CONFIRM" => GetMessage('CT_BNL_ELEMENT_DELETE_CONFIRM')));
$img = CFile::ResizeImageGet($arItem['PREVIEW_PICTURE'], array('width'=>375, 'height'=>370), BX_RESIZE_IMAGE_EXACT);
?>
<div class="news-wrap__item js-news-wrap__item ajax-item"
id="<?=$this->GetEditAreaId($arItem['ID']);?>">
<a href="<?=$arItem['DETAIL_PAGE_URL']?>" class="news-item js-lazy"
data-src="<?=$img['src']?>">
<div class="news-item__content-wrap">
<div class="news-item__text-wrap">
<h4 class="news-item__title"><?=$arItem['NAME']?></h4>
<h5 class="news-item__subtitle"><?=$arItem['PROPERTIES']['SUBTITLE']['VALUE']?></h5>
<p class="news-item__date"><?=strtolower($arItem['DISPLAY_ACTIVE_FROM'])?></p>
<p class="news-item__desc js-news-slider-desc">
<?=$arItem['~PREVIEW_TEXT']?>
</p>
</div>
<button class="news-item__button">подробнее</button>
</div>
</a>
<?
if(!empty($arParams["LIKED_NEWS_IDS"])) {
$arButtonData = array(
"LABEL" => GetMessage("LABEL_LIKE"),
"COLOR" => "green"
);
if (in_array($arItem['ID'], $arParams["LIKED_NEWS_IDS"])) {
$arButtonData = array(
"LABEL" => GetMessage("LABEL_DISLIKE"),
"COLOR" => "red"
);
}
?>
<div class="news-item__line">
<div class="news-item__input-holder">
<button class="btn btn_<?= $arButtonData["COLOR"]; ?>
btn_block send-btn js-like"
data-new-id="<?= $arItem['ID']; ?>"
data-iblock-id="<?= $arItem['IBLOCK_ID']; ?>"
data-like-label="<?=GetMessage("LABEL_LIKE");?>"
data-dislike-label="<?=GetMessage("LABEL_DISLIKE");?>"
data-handler-path="<?= $templateFolder . "/like.php"; ?>">
<?= $arButtonData["LABEL"]; ?>
</button>
</div>
</div>
<?
}
?>
<div class="news-item__line js-news-item__line">
<?=$arItem["PROPERTIES"]["LIKED_USERS"]["VALUE_LOGINS_STR"];?>
</div>
</div>
<?endforeach;?>
</div>
<?=$arResult['NAV_STRING']?>
|
result_modifier.php :
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
$arAllRegisteredUsersLogins = array();
$arAllRegisteredUsersLogins = HelperUsers::getAllRegisteredUsers(); // получаем логины всех зарегистрированных пользователей
foreach($arResult["ITEMS"] as &$arItem){
$arLikedUsersLogins = array();
sort($arItem["PROPERTIES"]["LIKED_USERS"]["VALUE"]);
foreach($arItem["PROPERTIES"]["LIKED_USERS"]["VALUE"] as $intLikedUserID){
$arLikedUsersLogins[] = $arAllRegisteredUsersLogins[$intLikedUserID];
}
$arItem["PROPERTIES"]["LIKED_USERS"]["VALUE_LOGINS_STR"] = implode(", ", $arLikedUsersLogins);
}
|
Метод HelperUsers::getAllRegisteredUsers :
class HelperUsers
{
/**
* @return mixed
*/
public static function getAllRegisteredUsers()
{
$arAllRegisteredUsersLogins = array();
$cache = new CPHPCache();
$cache_time = 86400;
$cache_id = 'arAllRegisteredUsersLogins_' . SITE_ID;
$cache_path = '/arAllRegisteredUsersLogins/';
if ($cache_time > 0 && $cache->InitCache($cache_time, $cache_id, $cache_path)) {
$res = $cache->GetVars();
if (is_array($res["arAllRegisteredUsersLogins"]) &&
(count($res["arAllRegisteredUsersLogins"]) > 0))
$arAllRegisteredUsersLogins = $res["arAllRegisteredUsersLogins"];
}
if (empty($arAllRegisteredUsersLogins)) {
$order = array('sort' => 'asc');
$tmp = 'sort';
$rsUsers = CUser::GetList($order, $tmp);
global $CACHE_MANAGER;
$CACHE_MANAGER->StartTagCache($cache_path);
while ($arrUser = $rsUsers->GetNext()) {
$arAllRegisteredUsersLogins[$arrUser["ID"]] = $arrUser["LOGIN"];
$CACHE_MANAGER->RegisterTag("RegisteredUser_" . $arrUser["ID"] . "_" . $arrUser["LOGIN"]);
}
$CACHE_MANAGER->EndTagCache();
if ($cache_time > 0) {
$cache->StartDataCache($cache_time, $cache_id, $cache_path);
$cache->EndDataCache(array("arAllRegisteredUsersLogins" => $arAllRegisteredUsersLogins));
}
}
return $arAllRegisteredUsersLogins;
}
} |
js код обработчика кнопки лайка:
$(function () {
(function () {
$(document).on("click", ".js-like", function () {
var $button = $(this);
var $parent = $button.closest(".js-news-wrap__item");
var $blockUsers = $parent.find(".js-news-item__line");
$.post($button.data("handler-path"), { newId: $button.data("new-id"), iblockId: $button.data("iblock-id") }, function (data) {
if (data.content){ $blockUsers.html(data.content); }
if (data.action === "like") {
$button.addClass("btn_red");
$button.removeClass("btn_green");
$button.html($button.data("dislike-label"));
} else if (data.action === "dislike") {
$button.removeClass("btn_red");
$button.addClass("btn_green");
$button.html($button.data("like-label"));
}
}, "json");
});
})();
}); |
в обработчике меняем цвет и надпись кнопки , а так-же содержимое блока, в котором выведены логины пользователей, лайкнувших новость.
like.php. обработчик лайка, который вызываем ajax-ом :
<?require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_before.php");
CModule::IncludeModule('iblock');
$newId = intVal(htmlspecialchars($_POST['newId']));
$iblockId = intVal(htmlspecialchars($_POST['iblockId']));
if(empty($newId)) die();
if(empty($iblockId)) die();
global $USER;
if ($USER->IsAuthorized()) {
$arAllRegisteredUsersLogins = array();
$arAllRegisteredUsersLogins = HelperUsers::getAllRegisteredUsers();
$arFilter = array(
"IBLOCK_ID" => $iblockId,
"ID" => $newId
);
$rsElement = CIBlockElement::GetList(
array(),
$arFilter,
false,
false,
array("ID", "IBLOCK_ID", "PROPERTY_LIKED_USERS")
);
$PROPERTY_LIKED_USERS = array();
while ($arElement = $rsElement->GetNext()) {
/** PROPERTY_LIKED_USERS - это множественное свойство, поэтому вместо if
* используется while */
$PROPERTY_LIKED_USERS[] = $arElement["PROPERTY_LIKED_USERS_VALUE"];
}
sort($PROPERTY_LIKED_USERS);
$strActionType = "like";
if (in_array($USER->GetID(), $PROPERTY_LIKED_USERS)) {
$PROPERTY_LIKED_USERS = array_diff($PROPERTY_LIKED_USERS, array($USER->GetID()));
$strActionType = "dislike";
} else {
$PROPERTY_LIKED_USERS = array_merge($PROPERTY_LIKED_USERS, array($USER->GetID()));
}
sort($PROPERTY_LIKED_USERS);
CIBlockElement::SetPropertyValueCode($newId, "LIKED_USERS", $PROPERTY_LIKED_USERS);
if (defined('BX_COMP_MANAGED_CACHE')) {
$GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_' . $iblockId);
$GLOBALS['CACHE_MANAGER']->ClearByTag('LikedNewID_' . $newId); // подозреваю, что эта строка здесь избыточна, но не стал проверять, оставил на всякий случай.
//Сброса кэша по стандартному тэгу iblock_id_ должно быть вполне достаточно.
}
$arLikedUsersLogins = array();
foreach ($PROPERTY_LIKED_USERS as $intLikedUserID) {
$arLikedUsersLogins[] = $arAllRegisteredUsersLogins[$intLikedUserID];
}
$VALUE_LOGINS_STR = implode(", ", $arLikedUsersLogins);
$result = array(
'action' => $strActionType,
'content' => $VALUE_LOGINS_STR
);
echo json_encode($result);
}else{
die();
}
|
Вот собственно и всё.
3) Ещё один вариант, который я спроектировал, но на практике не реализовывал.
Отказывается от параметра LIKED_NEWS_IDS и задействуем component_epilog. В нём через HelperIblock::getLikedNews получаем список id новостей, которые лайкнул текущий пользователь. Далее js-ом меняем цвет и надпись кнопок. js обработчик и ajax-обработчик лайка остаются такими-же, как в предыдущем варианте.
4) Посмотреть, как реализованы лайки в forum.topic.list