Есть следующий кейс:
Компонент 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
Код метода HelperIblock::getLikedNews :
template.php :
result_modifier.php :
Метод HelperUsers::getAllRegisteredUsers :
js код обработчика кнопки лайка:
в обработчике меняем цвет и надпись кнопки , а так-же содержимое блока, в котором выведены логины пользователей, лайкнувших новость.
like.php. обработчик лайка, который вызываем ajax-ом :
Вот собственно и всё.
3) Ещё один вариант, который я спроектировал, но на практике не реализовывал.
Отказывается от параметра LIKED_NEWS_IDS и задействуем component_epilog. В нём через HelperIblock::getLikedNews получаем список id новостей, которые лайкнул текущий пользователь. Далее js-ом меняем цвет и надпись кнопок. js обработчик и ajax-обработчик лайка остаются такими-же, как в предыдущем варианте.
4) Посмотреть, как реализованы лайки в forum.topic.list
Компонент 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