Explorar el Código

Ticket #4319 - Profiles: Improve Recommended profiles functionality.

AntonLV hace 3 años
padre
commit
2aac8c4fb6

+ 267 - 0
inc/classes/BxDolRecommendation.php

@@ -0,0 +1,267 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ */
+
+class BxDolRecommendation extends BxDolFactory implements iBxDolFactoryObject
+{
+    protected $_bIsApi;
+
+    protected $_oDb;
+
+    protected $_iObject;
+    protected $_sObject;
+    protected $_aObject;
+    protected $_aCriteria;
+
+    protected $_iProfileId;
+
+    protected $_iPerPageDefault;
+    protected $_iReducerDefault;
+
+    protected function __construct($aObject)
+    {
+        parent::__construct();
+
+        $this->_bIsApi = bx_is_api();
+
+        $this->_aObject = $aObject['object'];
+        $this->_iObject = (int)$this->_aObject['id'];
+        $this->_sObject = $this->_aObject['name'];
+        $this->_aCriteria = $aObject['criteria'];
+
+        $this->_oDb = new BxDolRecommendationQuery();
+        $this->_oDb->init($this->_aObject);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+
+        $this->_iPerPageDefault = 4;
+        $this->_iReducerDefault = 10;
+    }
+
+    public static function getObjectInstance($sObject)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolRecommendation!' . $sObject]))
+            return $GLOBALS['bxDolClasses']['BxDolRecommendation!' . $sObject];
+
+        $aObject = BxDolRecommendationQuery::getObject($sObject);
+        if(!$aObject || !is_array($aObject) || empty($aObject['object']))
+            return false;
+
+        $sClass = 'BxTemplRecommendation';
+        if(!empty($aObject['object']['class_name'])) {
+            $sClass = $aObject['object']['class_name'];
+            if(!empty($aObject['object']['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aObject['object']['class_file']);
+        }
+
+        $o = new $sClass($aObject);
+        return ($GLOBALS['bxDolClasses']['BxDolRecommendation!' . $sObject] = $o);
+    }
+
+    public static function updateData($iProfileId = 0)
+    {
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        BxDolCronQuery::getInstance()->addTransientJobService('recommendations_for_' . $iProfileId, ['system', 'updateRecommendations', [$iProfileId], 'TemplServiceProfiles']);
+    }
+
+    public function actionIgnore($iProfileId = 0, $iItemId = 0)
+    {
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        if(!$iItemId)
+            $iItemId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$this->ignore($iProfileId, $iItemId))
+            return ['msg' => '_sys_txt_error_occured'];
+
+        return ['code' => 0];
+    }
+
+    public function ignore($iProfileId, $iItemId)
+    {
+        $aItem = $this->_oDb->getItem($iProfileId, $this->_iObject, $iItemId);
+
+        $iReducer = !empty($aItem['item_reducer']) ? (int)$aItem['item_reducer'] : 1;
+        $iReducer *= $this->_iReducerDefault;
+
+        return $this->_oDb->update($iProfileId, $this->_iObject, $iItemId, ['item_reducer' => $iReducer]);
+    }
+
+    public function processCriteria($iProfileId, $iLimit = 100)
+    {
+        $aWeights = array_map(fn($fValue): float => $fValue * $iLimit, $this->_aObject['weights']);
+
+        $aCriterionItems = [];
+        foreach($this->_aCriteria as $sCriterion => $aCriterion) {
+            $iCriterionWeight = (float)$aCriterion['weight'];
+
+            $aParams = ['profile_id' => $iProfileId];
+            if(!empty($aCriterion['params'])) {
+                $aCriterionParams = unserialize($aCriterion['params']);
+                if(!empty($aCriterionParams) && is_array($aCriterionParams))
+                    $aParams = array_merge($aParams, $aCriterionParams);
+            }
+
+            switch($aCriterion['source_type']) {
+                case 'sql':
+                    $aCriterionItems[$sCriterion] = [];
+
+                    if(empty($aCriterion['source']))
+                        break;
+
+                    $sQuery = bx_replace_markers($aCriterion['source'], $aParams);
+                    if($this->_aObject['countable'])
+                        $sQuery .= ' ORDER BY `value` DESC';
+                    $sQuery .= ' LIMIT ' . $iLimit;
+
+                    $aCriterionItems[$sCriterion] = $this->_oDb->getPairs($sQuery, 'id', 'value');
+                    break;
+
+                case 'service':
+                    //TODO: Realize this!
+                    break;
+            }
+        }
+
+        $aResults = [];
+        foreach($aCriterionItems as $sCriteria => $aItems) {
+            $iCriteriaResults = 0;
+            foreach($aItems as $iId => $iValue) {
+                if(!isset($aResults[$iId]))
+                    $aResults[$iId] = 0;
+
+                $aResults[$iId] += $iValue;
+
+                if(++$iCriteriaResults >= $aWeights[$sCriteria])
+                    break;
+            }
+        }
+
+        $this->_oDb->clean($iProfileId, $this->_iObject);
+
+        foreach($aResults as $iId => $iValue)
+            $this->_oDb->add($iProfileId, $this->_iObject, $iId, $iValue);
+    }
+
+    public function outputActionResult ($mixed, $sFormat = 'json')
+    {
+        switch ($sFormat) {
+            case 'html':
+                echo $mixed;
+                break;
+                
+            case 'json':
+            default:
+                header('Content-Type: application/json; charset=utf-8');
+                echo json_encode($mixed);     
+        }
+        exit;
+    }
+
+    protected function _getContextName()
+    {
+        return str_replace('sys_', 'recom_', $this->_sObject); 
+    }
+
+    /**
+     * The first variant. Isn't used.
+     */
+    public function processCriteriaForSelection($iProfileId, $iStart, $iPerPage, &$bShowPaginate)
+    {
+        if(($iStartGet = bx_get('start')) !== false)
+            $iStart = (int)bx_get('start');
+
+        $aStarts = [];
+        if(($sStartsGet = bx_get('starts')) !== false)
+            $aStarts = array_combine(array_keys($this->_aCriteria), json_decode($sStartsGet, true));
+
+        $aWeights = array_map(fn($fValue): float => $fValue * $iPerPage, $this->_aObject['weights']);
+
+        $aCriterionItems = [];
+        foreach($this->_aCriteria as $sCriterion => $aCriterion) {
+            $iCriterionWeight = (float)$aCriterion['weight'];
+
+            $aParams = ['profile_id' => $iProfileId];
+            if(!empty($aCriterion['params'])) {
+                $aCriterionParams = unserialize($aCriterion['params']);
+                if(!empty($aCriterionParams) && is_array($aCriterionParams))
+                    $aParams = array_merge($aParams, $aCriterionParams);
+            }
+
+            $iCriterionStart = isset($aStarts[$sCriterion]) ? (int)$aStarts[$sCriterion] : $iStart * $iCriterionWeight;
+
+            switch($aCriterion['source_type']) {
+                case 'sql':
+                    $aCriterionItems[$sCriterion] = [];
+
+                    if(empty($aCriterion['source']))
+                        break;
+
+                    $sQuery = bx_replace_markers($aCriterion['source'], $aParams);
+                    if($this->_aObject['countable'])
+                        $sQuery .= ' ORDER BY `value` DESC';
+                    $sQuery .= ' LIMIT ' . $iCriterionStart . ', ' . $iPerPage;
+
+                    $aCriterionItems[$sCriterion] = $this->_oDb->getPairs($sQuery, 'id', 'value');
+                    break;
+
+                case 'service':
+                    //TODO: Realize this!
+                    break;
+            }           
+        }
+
+        $aResults = [];
+        $aHeap = [];
+        $aStats = [];
+        foreach($aCriterionItems as $sCriteria => $aItems) {
+            $iCriteriaAdded = 0;
+            $iCriteriaUpdated = 0;
+            foreach($aItems as $iId => $iValue) {
+                $bAddded = isset($aResults[$iId]);
+                if(!$bAddded && $iCriteriaAdded < $aWeights[$sCriteria]) {
+                    $aResults[$iId] = $iValue;
+                    $iCriteriaAdded++;
+                }
+                else {
+                    if($bAddded) {
+                        $aResults[$iId] += $iValue;
+                        $iCriteriaUpdated++;
+                    }
+                    else
+                        $aHeap[$sCriteria][$iId] = $iValue;
+                }
+            }
+
+            $aStats[$sCriteria] = $iCriteriaAdded + $iCriteriaUpdated;
+        }
+
+        $iResults = count($aResults);
+        if($iResults < $iPerPage && !empty($aHeap)) {
+            foreach($aHeap as $sCriteria => $aItems) {
+                if(!$aItems)
+                    continue;
+
+                arsort($aItems);
+
+                foreach($aItems as $iId => $iValue) {
+                    $aResults[$iId] = $iValue;
+
+                    $aStats[$sCriteria]++;
+                    if(++$iResults == $iPerPage)
+                        break;
+                }
+            }
+        }
+
+        arsort($aResults);
+        return $aResults;
+    }
+}
+
+/** @} */

+ 118 - 0
inc/classes/BxDolRecommendationQuery.php

@@ -0,0 +1,118 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+class BxDolRecommendationQuery extends BxDolDb
+{
+    public static $sTableObjects = 'sys_objects_recommendation';
+    public static $sTableCriteria = 'sys_recommendation_criteria';
+    public static $sTableData = 'sys_recommendation_data';
+            
+    protected $_aObject;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->_aObject = [];
+    }
+
+    static public function getObjects($bActiveOnly = true)
+    {
+        $sWhereClause = "";
+        if($bActiveOnly)
+            $sWhereClause = " AND `active` = 1";
+
+        return BxDolDb::getInstance()->getAllWithKey("SELECT * FROM `" . self::$sTableObjects . "` WHERE 1" . $sWhereClause, 'name');
+    }
+
+    static public function getObject($sName)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $aObject = $oDb->getRow("SELECT * FROM `" . self::$sTableObjects . "` WHERE `name` = :name", ['name' => $sName]);
+        if(!$aObject || !is_array($aObject))
+            return false;
+        
+        $aCriteria = $oDb->getAllWithKey("SELECT * FROM `" . self::$sTableCriteria . "` WHERE `object_id` = :object_id AND `weight` > 0 AND `active` = 1 ORDER BY `weight` DESC", 'name', ['object_id' => $aObject['id']]);
+        if(!$aCriteria || !is_array($aCriteria))
+            return false;
+
+        $aObject['weights'] = $oDb->getPairs("SELECT `name`, `weight` FROM `" . self::$sTableCriteria . "` WHERE `object_id` = :object_id AND `weight` > 0 AND `active` = 1 ORDER BY `weight` DESC", 'name', 'weight', ['object_id' => $aObject['id']]);
+
+        return [
+            'object' => $aObject,
+            'criteria' => $aCriteria
+        ];
+    }
+
+    public function init($aObject = [])
+    {
+        if(empty($aObject) || !is_array($aObject)) 
+            return;
+
+        $this->_aObject = $aObject;
+    }
+
+    public function clean($iProfileId, $iObjectId, $bAll = false)
+    {
+        $sWhereClause = "";
+        if(!$bAll)
+            $sWhereClause = " AND `item_reducer` = 0";
+
+        return $this->query("DELETE FROM `" . self::$sTableData . "` WHERE `profile_id` = :profile_id AND `object_id` = :object_id" . $sWhereClause, [
+            'profile_id' => $iProfileId,
+            'object_id' => $iObjectId
+        ]);
+    }
+
+    public function add($iProfileId, $iObjectId, $iItemId, $iItemValue)
+    {
+        return $this->query("INSERT INTO `" . self::$sTableData . "` (`profile_id`, `object_id`, `item_id`, `item_value`) VALUES (:profile_id, :object_id, :item_id, :item_value) ON DUPLICATE KEY UPDATE `item_value` = :item_value", [
+            'profile_id' => $iProfileId,
+            'object_id' => $iObjectId,
+            'item_id' => $iItemId, 
+            'item_value' => $iItemValue
+        ]) !== false;
+    }
+    
+    public function update($iProfileId, $iObjectId, $iItemId, $aSet)
+    {
+        if(empty($aSet) || !is_array($aSet))
+            return false;
+
+        return $this->query("UPDATE `" . self::$sTableData . "` SET " . $this->arrayToSQL($aSet) . " WHERE `profile_id` = :profile_id AND `object_id` = :object_id AND `item_id` = :item_id ", [
+            'profile_id' => $iProfileId,
+            'object_id' => $iObjectId,
+            'item_id' => $iItemId
+        ]) !== false;
+    }
+
+    public function get($iProfileId, $iObjectId, $iStart = 0, $iPerPage = 0)
+    {
+        $sLimitClause = "";
+        if($iPerPage)
+            $sLimitClause = " LIMIT " . $iStart . ", " . $iPerPage;
+
+        return $this->getPairs("SELECT `item_id` AS `id`, (`item_value` - `item_reducer`) AS `value` FROM `" . self::$sTableData . "` WHERE `profile_id` = :profile_id AND `object_id` = :object_id AND (`item_value` - `item_reducer`) > 0 ORDER BY `value` DESC" . $sLimitClause, 'id', 'value', [
+            'profile_id' => $iProfileId,
+            'object_id' => $iObjectId
+        ]);
+    }
+
+    public function getItem($iProfileId, $iObjectId, $iItemId)
+    {
+        return $this->getRow("SELECT * FROM `" . self::$sTableData . "` WHERE `profile_id` = :profile_id AND `object_id` = :object_id AND `item_id` = :item_id", [
+            'profile_id' => $iProfileId,
+            'object_id' => $iObjectId,
+            'item_id' => $iItemId
+        ]);
+    }
+}
+
+/** @} */

+ 50 - 0
inc/js/functions.js

@@ -1049,6 +1049,56 @@ function bx_conn_action(e, sObj, sAction, iContentId, bConfirm, fOnComplete) {
     	fPerform();
 }
 
+/**
+ * Perform recommendations AJAX request. 
+ * In case of error - it shows js alert with error message.
+ * In case of success - the page is reloaded.
+ * 
+ * @param sObj - recommendation object
+ * @param sAction - 'ignore'
+ * @param iContentId - content id, initiator is always current logged in user
+ * @param bConfirm - show confirmation dialog
+ */
+function bx_recommendation_action(e, sObj, sAction, iContentId, bConfirm, fOnComplete) {
+    var fPerform = function() {
+        var aParams = {
+            obj: sObj,
+            act: sAction,
+            id: iContentId
+        };
+
+        var fCallback = function (data) {
+            bx_loading_btn(e, 0);
+            if ('object' != typeof(data))
+                return;
+
+            if (data.msg)
+                bx_alert(data.msg);
+            else {
+                if ('function' == typeof(fOnComplete))
+                    fOnComplete(data, e);
+                else if (!loadDynamicBlockAuto(e))
+                    location.reload();
+            }
+        };
+
+        bx_loading_btn(e, 1);
+
+        $.ajax({
+            dataType: 'json',
+            url: sUrlRoot + 'recommendation.php',
+            data: aParams,
+            type: 'POST',
+            success: fCallback
+        });
+    };
+
+    if (typeof(bConfirm) != 'undefined' && bConfirm)
+    	bx_confirm(_t('_Are_you_sure'), fPerform);
+    else
+    	fPerform();
+}
+
 function bx_append_url_params (sUrl, mixedParams) {
     var sParams = sUrl.indexOf('?') == -1 ? '?' : '&';
 

+ 8 - 7
inc/profiles.inc.php

@@ -120,14 +120,15 @@ function bx_login($iId, $bRememberMe = false)
 
     $oAccountQuery->updateLoggedIn($iId);
 
+    if(($iProfileId = BxDolProfileQuery::getInstance()->getCurrentProfileByAccount($iId, true)) !== false)
+        BxDolRecommendation::updateData($iProfileId);
+
     bx_alert('account', 'login',  $iId);
-    
-    bx_audit(
-        $iId, 
-        'bx_accounts', 
-        '_sys_audit_action_account_login',  
-        array('content_title' => '', 'data' => ['display_info' => ['User agent' => (isset($_SERVER["HTTP_USER_AGENT"]) ? $_SERVER["HTTP_USER_AGENT"] : '')]])
-    );
+
+    bx_audit($iId, 'bx_accounts', '_sys_audit_action_account_login',  array(
+        'content_title' => '', 
+        'data' => ['display_info' => ['User agent' => (isset($_SERVER["HTTP_USER_AGENT"]) ? $_SERVER["HTTP_USER_AGENT"] : '')]]
+    ));
     
     return $oAccountQuery->getInfoById($iId);
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
install/sql/system.sql


+ 32 - 0
modules/base/profile/classes/BxBaseModProfileMenuSnippetMeta.php

@@ -42,6 +42,11 @@ class BxBaseModProfileMenuSnippetMeta extends BxBaseModGeneralMenuSnippetMeta
         return 'bx_conn_action(this, \'' . $sConnection . '\', \'' . $sAction . '\', \'' . $iContentProfile . '\', false, function(oData, eLink) {$(eLink).parents(\'.bx-menu-item:first\').remove();})';
     }
 
+    protected function getMenuItemRecommendationJsCode($sObject, $sAction, $iContentId, $aItem)
+    {
+        return 'bx_recommendation_action(this, \'' . $sObject . '\', \'' . $sAction . '\', \'' . $iContentId . '\', false, function(oData, eLink) {$(eLink).parents(\'.bx-base-pofile-unit-with-cover:first\').remove();})';
+    }
+
     public function setContentPublic($bContentPublic)
     {
         $this->_bContentPublic = $bContentPublic;
@@ -67,6 +72,33 @@ class BxBaseModProfileMenuSnippetMeta extends BxBaseModGeneralMenuSnippetMeta
         return $this->_getMenuItemConnection('sys_profiles_subscriptions', 'remove', $aItem);
     }
 
+    protected function _getMenuItemIgnoreBefriend($aItem)
+    {
+        return $this->_getMenuItemIgnoreRecommendation('sys_friends', $aItem);
+    }
+
+    protected function _getMenuItemIgnoreSubscribe($aItem)
+    {
+        return $this->_getMenuItemIgnoreRecommendation('sys_subscriptions', $aItem);
+    }
+
+    protected function _getMenuItemIgnoreRecommendation($sObject, $aItem)
+    {
+        if(!$this->_isVisibleInContext($aItem))
+            return false;
+
+        $oRecommendation = BxDolRecommendation::getObjectInstance($sObject);
+        if(!$oRecommendation)
+            return false;
+
+        $mixedItem = $this->getUnitMetaItemButton(_t($aItem['title']), [
+            'class' => !empty($aItem['primary']) ? 'bx-btn-primary' : '',
+            'onclick' => $this->getMenuItemRecommendationJsCode($sObject, 'ignore', $this->_oContentProfile->id(), $aItem)
+        ]);
+
+        return $mixedItem !== false ? [$mixedItem, 'bx-menu-item-button'] : false;
+    }
+
     protected function _getMenuItemFriends($aItem)
     {
         if(!$this->_isVisibleInContext($aItem))

+ 6 - 0
modules/boonex/english/data/langs/system/en.xml

@@ -2923,6 +2923,7 @@ If it is not enabled then please consider implement this optimization, since it
     <string name="_sys_menu_item_title_sm_subscribers"><![CDATA[{0} followers]]></string>
     <string name="_sys_menu_item_title_sm_country"><![CDATA[Country]]></string>
     <string name="_sys_menu_item_title_sm_country_city"><![CDATA[Country and City]]></string>
+    <string name="_sys_menu_item_title_sm_ignore"><![CDATA[Remove]]></string>
     <string name="_sys_menu_item_title_vm_membership"><![CDATA[Membership]]></string>
     <string name="_sys_menu_item_title_vm_badges"><![CDATA[Badges]]></string>
     <string name="_sys_menu_item_title_system_about"><![CDATA[About]]></string>
@@ -3042,6 +3043,7 @@ If it is not enabled then please consider implement this optimization, since it
     <string name="_sys_menu_item_title_system_sm_unsubscribe"><![CDATA[Unfollow]]></string>
     <string name="_sys_menu_item_title_system_sm_country"><![CDATA[Country]]></string>
     <string name="_sys_menu_item_title_system_sm_country_city"><![CDATA[Country and City]]></string>
+    <string name="_sys_menu_item_title_system_sm_ignore"><![CDATA[Ignore Recommendation]]></string>
     <string name="_sys_menu_item_title_system_vm_membership"><![CDATA[Membership]]></string>
     <string name="_sys_menu_item_title_system_vm_badges"><![CDATA[Badges]]></string>
     
@@ -3225,6 +3227,10 @@ If it is not enabled then please consider implement this optimization, since it
     <string name="_sys_page_block_title_create_account"><![CDATA[Sign up]]></string>
     <string name="_sys_page_block_title_sys_author"><![CDATA[Author]]></string>
     <string name="_sys_page_block_title_author"><![CDATA[Author]]></string>
+    <string name="_sys_page_block_title_sys_recom_friends"><![CDATA[Recommended Friends]]></string>
+    <string name="_sys_page_block_title_recom_friends"><![CDATA[Friends Suggestions]]></string>
+    <string name="_sys_page_block_title_sys_recom_subscriptions"><![CDATA[Recommended Subscriptions]]></string>
+    <string name="_sys_page_block_title_recom_subscriptions"><![CDATA[Who to follow]]></string>
     <string name="_sys_page_block_title_sys_create_post"><![CDATA[Create Post]]></string>
     <string name="_sys_page_block_title_create_post"><![CDATA[Add new...]]></string>
     <string name="_sys_page_block_title_sys_create_post_context"><![CDATA[Create Post (Context)]]></string>

+ 6 - 0
modules/boonex/russian/data/langs/system/ru.xml

@@ -2890,6 +2890,7 @@
     <string name="_sys_menu_item_title_sm_subscribers"><![CDATA[Подписчики: {0}]]></string>
     <string name="_sys_menu_item_title_sm_country"><![CDATA[Страна]]></string>
     <string name="_sys_menu_item_title_sm_country_city"><![CDATA[Страна и город]]></string>
+    <string name="_sys_menu_item_title_sm_ignore"><![CDATA[Удалить]]></string>
     <string name="_sys_menu_item_title_vm_membership"><![CDATA[Членство]]></string>
     <string name="_sys_menu_item_title_vm_badges"><![CDATA[Значки]]></string>
     <string name="_sys_menu_item_title_system_about"><![CDATA[Об...]]></string>
@@ -3006,6 +3007,7 @@
     <string name="_sys_menu_item_title_system_sm_members"><![CDATA[Участники]]></string>
     <string name="_sys_menu_item_title_system_sm_membership"><![CDATA[Членство]]></string>
     <string name="_sys_menu_item_title_system_sm_subscribers"><![CDATA[Подписки]]></string>
+    <string name="_sys_menu_item_title_system_sm_ignore"><![CDATA[Игнорировать рекомендацию]]></string>
     <string name="_sys_menu_item_title_system_vm_membership"><![CDATA[Членство]]></string>
     <string name="_sys_menu_item_title_system_vm_badges"><![CDATA[Значки]]></string>
     <string name="_sys_menu_set_title_account_dashboard"><![CDATA[Панель аккаунта]]></string>
@@ -3180,6 +3182,10 @@
 	<string name="_sys_page_block_title_create_account"><![CDATA[Присоединиться]]></string>
         <string name="_sys_page_block_title_sys_author"><![CDATA[Автор]]></string>
 	<string name="_sys_page_block_title_author"><![CDATA[Автор]]></string>
+        <string name="_sys_page_block_title_sys_recom_friends"><![CDATA[Рекомендуемые друзья]]></string>
+        <string name="_sys_page_block_title_recom_friends"><![CDATA[С кем дружить]]></string>
+        <string name="_sys_page_block_title_sys_recom_subscriptions"><![CDATA[Рекомендуемые подписки]]></string>
+        <string name="_sys_page_block_title_recom_subscriptions"><![CDATA[На кого подписаться]]></string>
         <string name="_sys_page_block_title_sys_create_post"><![CDATA[Создать публикацию]]></string>
 	<string name="_sys_page_block_title_create_post"><![CDATA[Добавить...]]></string>
 	<string name="_sys_page_block_title_sys_create_post_context"><![CDATA[Создать публикацию (в контекст)]]></string>

+ 31 - 0
recommendation.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+check_logged();
+
+$sObj = bx_get('obj') ? bx_get('obj') : '';
+$sAct = bx_get('act') && preg_match ('/^[A-Za-z_]+$/', bx_get('act')) ? bx_get('act') : '';
+$sFmt = bx_get('fmt') ? bx_get('fmt') : 'json';
+
+$oRecom = BxDolRecommendation::getObjectInstance($sObj);
+
+$sMethod = 'action' . $sAct;
+if ($oRecom && $sAct && method_exists($oRecom, $sMethod)) {
+    echo $oRecom->outputActionResult($oRecom->$sMethod(), $sFmt);
+    exit;
+} 
+else {
+    $oTemplate = BxDolTemplate::getInstance();
+    $oTemplate->displayPageNotFound();
+}
+
+/** @} */

+ 13 - 1
template/scripts/BxBaseMenuUnitMeta.php

@@ -257,10 +257,22 @@ class BxBaseMenuUnitMeta extends BxTemplMenuCustom
 
     protected function _isVisibleInContext($aItem)
     {
+        $sDiv = ',';
+
         if(empty($this->_sContext) || empty($aItem['hidden_on_cxt']))
             return true;
 
-        $aContexts = explode(',', $aItem['hidden_on_cxt']);
+        //--- Hide everywhere.
+        if($aItem['hidden_on_cxt'] == 'all')
+            return false;
+
+        //--- Hide everywhere except following list.
+        if(substr($aItem['hidden_on_cxt'], 0, 4) == 'all!') {
+            $aContexts = explode($sDiv, substr($aItem['hidden_on_cxt'], 4));
+            return in_array($this->_sContext, $aContexts);
+        }
+
+        $aContexts = explode($sDiv, $aItem['hidden_on_cxt']);
         return !in_array($this->_sContext, $aContexts);
     }
 }

+ 96 - 0
template/scripts/BxBaseRecommendation.php

@@ -0,0 +1,96 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Recommendation representation.
+ * @see BxDolRecommendation
+ */
+class BxBaseRecommendation extends BxDolRecommendation
+{
+    protected $_oTemplate;
+
+    protected $_sClassWrapper;
+
+    public function __construct ($aOptions, $oTemplate)
+    {
+        parent::__construct ($aOptions);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+    }
+
+    public function getCode($iProfileId = 0, $iStart = 0, $iPerPage = 0)
+    {
+        $sResult = '';
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        if(!$iPerPage)
+            $iPerPage = $this->_iPerPageDefault;
+
+        $aItems = $this->_oDb->get($iProfileId, $this->_aObject['id'], $iStart, $iPerPage + 1);
+        if(empty($aItems) || !is_array($aItems))
+            return '';
+
+        $iItems = count($aItems);
+        $oPaginate = new BxTemplPaginate([
+            'on_change_page' => "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}')",
+            'num' => $iItems,
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+        ]);
+        $sPaginate = $oPaginate->getSimplePaginate();
+
+        if($iItems > $iPerPage)
+            array_pop($aItems);
+
+        foreach($aItems as $iId => $iCount)
+            $sResult .= $this->getCodeItem($iId, $iCount);
+
+        return $this->_oTemplate->parseHtmlByName('recommendation_block.html', [
+            'class' => $this->_sClassWrapper,
+            'content' => $sResult,
+            'bx_if:show_paginate' => [
+                'condition' => !empty($sPaginate),
+                'content' => [
+                    'paginate' => $sPaginate
+                ]
+            ]
+        ]);
+    }
+    
+    public function getCodeAPI($iProfileId = 0, $iStart = 0, $iPerPage = 0)
+    {
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        if(!$iPerPage)
+            $iPerPage = $this->_iPerPageDefault;
+
+        $aItems = $this->_oDb->get($iProfileId, $this->_aObject['id'], $iStart, $iPerPage);
+
+        $aData = [];
+        foreach($aItems as $iId => $iCount)
+            $aData[] = $this->getCodeItem($iId, $iCount);
+
+        return [
+            'request_url' => '',
+            'data' => $aData,
+            'params' => [
+                'per_page' => $iStart,
+                'start' => $iPerPage,
+            ],
+        ];
+    }
+}
+
+/** @} */

+ 36 - 0
template/scripts/BxBaseRecommendationProfile.php

@@ -0,0 +1,36 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Profile recommendation representation.
+ * @see BxDolRecommendation
+ */
+class BxBaseRecommendationProfile extends BxTemplRecommendation
+{
+    public function __construct ($aOptions, $oTemplate)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sClassWrapper = 'bx-base-pofile-units-wrapper bx-base-puw-gallery';
+    }
+
+    public function getCodeItem($iId, $iCount)
+    {
+        $oProfile = BxDolProfile::getInstance($iId);
+        if(!$oProfile)
+            return '';
+
+        return $oProfile->{'getUnit' . ($this->_bIsApi ? 'API' : '')}(0, [
+            'template' => 'unit_with_cover', 
+            'context' => $this->_getContextName()
+        ]);
+    }
+}
+
+/** @} */

+ 51 - 0
template/scripts/BxBaseServiceProfiles.php

@@ -575,6 +575,50 @@ class BxBaseServiceProfiles extends BxDol
         return $this->_serviceBrowseConnections('connections', $aParamsBrowse, $aParams['design_box'], $aParams['empty_message'], $aParams['ajax_paginate']);
     }
 
+    public function serviceBrowseRecommendationsFriends ($iProfileId = 0, $iStart = 0, $iPerPage = 0)
+    {
+        if(($iStartGet = bx_get('start')) !== false)
+            $iStart = (int)$iStartGet;
+
+        if(($iPerPageGet = bx_get('per_page')) !== false)
+            $iPerPage = (int)$iPerPageGet;
+
+        $oRecommendation = BxDolRecommendation::getObjectInstance('sys_friends');
+        if(!$oRecommendation)
+            return false;
+
+        if(bx_is_api()) {
+            $aData = $oRecommendation->getCodeAPI($iProfileId, $iStart, $iPerPage);
+            $aData['request_url'] = '/api.php?r=system/browse_recommendations_friends/TemplServiceProfiles&params[]=';
+
+            return [bx_api_get_block('browse', $aData)];
+        }
+
+        return $oRecommendation->getCode($iProfileId, $iStart, $iPerPage);
+    }
+
+    public function serviceBrowseRecommendationsSubscriptions ($iProfileId = 0, $iStart = 0, $iPerPage = 0)
+    {
+        if(($iStartGet = bx_get('start')) !== false)
+            $iStart = (int)$iStartGet;
+
+        if(($iPerPageGet = bx_get('per_page')) !== false)
+            $iPerPage = (int)$iPerPageGet;
+
+        $oRecommendation = BxDolRecommendation::getObjectInstance('sys_subscriptions');
+        if(!$oRecommendation)
+            return false;
+
+        if(bx_is_api()) {
+            $aData = $oRecommendation->getCodeAPI($iProfileId, $iStart, $iPerPage);
+            $aData['request_url'] = '/api.php?r=system/browse_recommendations_subscriptions/TemplServiceProfiles&params[]=';
+
+            return [bx_api_get_block('browse', $aData)];
+        }
+
+        return $oRecommendation->getCode($iProfileId, $iStart, $iPerPage);
+    }
+
     public function serviceAccountProfileSwitcher ($iAccountId = false, $iActiveProfileId = null, $sUrlProfileAction = '', $bShowAll = 0, $sButtonTitle = '', $sProfileTemplate = '')
     {
     	$oTemplate = BxDolTemplate::getInstance();
@@ -732,6 +776,13 @@ class BxBaseServiceProfiles extends BxDol
         return BxDolContentFilter::getInstance()->isEnabled();
     }
 
+    public function serviceUpdateRecommendations($iProfileId)
+    {
+        $aObjects = BxDolRecommendationQuery::getObjects();
+        foreach($aObjects as $aObject)
+            BxDolRecommendation::getObjectInstance($aObject['name'])->processCriteria($iProfileId);
+    }
+
     protected function _getIcon ($sIcon)
     {
         return BxTemplFunctions::getInstance()->getIcon($sIcon);

+ 1 - 0
template/scripts/BxBaseServices.php

@@ -65,6 +65,7 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
             'ProfileMembership' => 'BxBaseServiceProfiles',
             'ProfileNotifications' => 'BxBaseServiceProfiles',
             'GetCountOnlineProfiles' => 'BxBaseServiceProfiles',
+            'BrowseRecommendationsFriends' => 'BxBaseServiceProfiles',
 
             'GetChartGrowth' => 'BxBaseChartServices',
             'GetChartStats' => 'BxBaseChartServices',

+ 21 - 0
template/scripts_templ/BxTemplRecommendation.php

@@ -0,0 +1,21 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaTemplate UNA Template Classes
+ * @{
+ */
+
+/**
+ * @see BxDolRecommendation
+ */
+class BxTemplRecommendation extends BxBaseRecommendation
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+    }
+}
+
+/** @} */

+ 21 - 0
template/scripts_templ/BxTemplRecommendationProfile.php

@@ -0,0 +1,21 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaTemplate UNA Template Classes
+ * @{
+ */
+
+/**
+ * @see BxDolRecommendation
+ */
+class BxTemplRecommendationProfile extends BxBaseRecommendationProfile
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+    }
+}
+
+/** @} */

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio