AntonLV 7 роки тому
батько
коміт
4c0cbf5446
29 змінених файлів з 1216 додано та 64 видалено
  1. 223 3
      inc/classes/BxDolPrivacy.php
  2. 135 0
      inc/classes/BxDolPrivacyQuery.php
  3. 155 0
      inc/js/classes/BxDolPrivacy.js
  4. 0 0
      install/sql/system.sql
  5. 8 0
      modules/base/general/classes/BxBaseModGeneralFormEntry.php
  6. 16 0
      modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php
  7. 3 2
      modules/base/general/classes/BxBaseModGeneralModule.php
  8. 18 0
      modules/base/groups/classes/BxBaseModGroupsFormsEntryHelper.php
  9. 8 0
      modules/base/profile/classes/BxBaseModProfileFormEntry.php
  10. 48 16
      modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php
  11. 8 0
      modules/base/profile/classes/BxBaseModProfilePrivacy.php
  12. 8 0
      modules/base/profile/classes/BxBaseModProfilePrivacyPost.php
  13. 4 10
      modules/base/text/classes/BxBaseModTextFormsEntryHelper.php
  14. 21 0
      modules/boonex/decorous/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.php
  15. 21 2
      modules/boonex/english/data/langs/system/en.xml
  16. 21 0
      modules/boonex/lucid/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.php
  17. 21 0
      modules/boonex/ocean/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.php
  18. 21 0
      modules/boonex/protean/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.php
  19. 23 4
      modules/boonex/russian/data/langs/system/ru.xml
  20. 29 0
      privacy.php
  21. 12 4
      template/css/forms.css
  22. 22 0
      template/css/privacy.css
  23. 14 9
      template/form_field_custom_suggestions.html
  24. 1 0
      template/privacy_group_custom.html
  25. 8 0
      template/privacy_users_select_list.html
  26. 20 0
      template/privacy_users_select_popup.html
  27. 39 9
      template/scripts/BxBaseFormView.php
  28. 113 5
      template/scripts/BxBasePrivacy.php
  29. 196 0
      template/scripts/BxBasePrivacyFormGroupCustom.php

+ 223 - 3
inc/classes/BxDolPrivacy.php

@@ -12,6 +12,10 @@ define('BX_DOL_PG_MEONLY', '2');
 define('BX_DOL_PG_ALL', '3');
 define('BX_DOL_PG_MEMBERS', '4');
 define('BX_DOL_PG_FRIENDS', '5');
+define('BX_DOL_PG_FRIENDS_SELECTED', '6');
+define('BX_DOL_PG_RELATIONS', '7');
+define('BX_DOL_PG_RELATIONS_SELECTED', '8');
+define('BX_DOL_PG_CUSTOM', '9');
 
 define('BX_DOL_PG_DEFAULT', BX_DOL_PG_ALL);
 
@@ -63,7 +67,11 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
     protected $_sObject;
     protected $_aObject;
 
+    protected $_aGroupsSettings;
     protected $_aGroupsExclude;
+    
+    protected $_sFormUsers;
+    protected $_sFormDisplayUsersSelect;
 
     /**
      * Constructor
@@ -79,7 +87,15 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
         $this->_oDb = new BxDolPrivacyQuery();
         $this->_oDb->init($this->_aObject);
 
+        $this->_aGroupsSettings = array(
+            BX_DOL_PG_FRIENDS_SELECTED => array('connection' => 'sys_profiles_friends'),
+            BX_DOL_PG_RELATIONS_SELECTED => array('connection' => 'sys_profiles_relations')
+        );
+
         $this->_aGroupsExclude = array();
+
+        $this->_sFormUsers = 'sys_privacy_group_custom';
+        $this->_sFormDisplayUsersSelect = 'sys_privacy_group_custom_manage';
     }
 
     /**
@@ -147,12 +163,18 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
             $sTitle = _t(!empty($sTitle) ? $sTitle : '_' . $sName);
         }
 
+        BxDolTemplate::getInstance()->addInjection('system_injection_header', 'text', $oPrivacy->getJsScript());
+
         return array(
             'type' => 'select',
             'name' => $sName,
             'caption' => $sTitle,
             'value' => $sValue,
             'values' => $aValues,
+            'attrs' => array(
+                'class' => 'sys-privacy-group',
+                'onchange' => 'javascript: ' . $oPrivacy->getJsObjectName() . '.selectGroup(this);'
+            ),
             'checker' => array(
                 'func' => 'avail',
                 'error' => _t('_sys_ps_ferr_incorrect_select')
@@ -163,6 +185,150 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
         );
     }
 
+    public function actionLoadGroupCustom()
+    {
+        $iProfileId = (int)bx_get('profile_id');
+        $iContentId = (int)bx_get('content_id');
+        $iGroupId = (int)bx_get('group_id');
+
+        $oForm = BxDolForm::getObjectInstance($this->_sFormUsers, $this->_sFormDisplayUsersSelect);
+        return echoJson(array('eval' => $this->getJsObjectName() . '.onSelectGroup(oData);', 'content' => $oForm->getElementGroupCustom(array(
+            'profile_id' => $iProfileId, 
+            'content_id' => $iContentId, 
+            'object' => $this->_sObject, 
+            'group_id' => $iGroupId
+        ))));
+    }
+
+    public function actionSelectGroup()
+    {
+        $aValues = array(
+            'profile_id' => (int)bx_get('profile_id'),
+            'content_id' => (int)bx_get('content_id'),
+            'object' => $this->_sObject,
+            'group_id' => (int)bx_get('group_id')
+        );
+
+        $aParams = array(
+            'popup_only' => (bool)bx_get('popup_only')
+        );
+
+        if(!isset($this->_aGroupsSettings[$aValues['group_id']]))
+            return echoJson(array());
+
+        if($aValues['profile_id'] != bx_get_logged_profile_id())
+            return echoJson(array('msg' => _t('_sys_ps_ferr_incorrect_gc_owner')));
+
+        if(($mixedResult = $this->isSelectGroupCustom($aValues)) !== true)
+            return echoJson(array('msg' => $mixedResult));
+
+        return echoJson($this->getSelectGroup($aValues, $aParams));
+    }
+
+    public function actionUsersList()
+    {
+        $iGroup = (int)bx_get('group');
+        if(!isset($this->_aGroupsSettings[$iGroup]))
+            return echoJson(array());
+
+        $oConnection = BxDolConnection::getObjectInstance($this->_aGroupsSettings[$iGroup]['connection']);
+        if(!$oConnection)
+            return echoJson(array());
+
+        $iProfileId = bx_get_logged_profile_id();
+        $aConnectedIds = $oConnection->getConnectedContent($iProfileId, true);
+        if(empty($aConnectedIds) || !is_array($aConnectedIds))
+            return echoJson(array());
+
+        $sTerm = bx_get('term');
+        $aProfiles = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+        if(empty($aProfiles))
+            return echoJson(array());
+        
+        $aResult = array();
+        foreach($aProfiles as $aProfile)
+            if(in_array($aProfile['value'], $aConnectedIds))
+                $aResult[] = $aProfile;
+
+        echoJson($aResult);
+    }  
+
+    public function loadGroupCustom($iProfileId, $iContentId, $iGroupId)
+    {
+        $sJsCode = $this->_oTemplate->_wrapInTagJsCode($this->getJsObjectName() . ".loadGroupCustom(" . json_encode(array(
+            'profile_id' => $iProfileId,
+            'content_id' => $iContentId,
+            'group_id' => $iGroupId
+        )) . ");");
+
+        BxDolTemplate::getInstance()->addInjection('system_injection_header', 'text', $sJsCode);
+    }
+
+    public function getGroupSettings($iGroup)
+    {
+        if(empty($this->_aGroupsSettings[$iGroup]) || !is_array($this->_aGroupsSettings[$iGroup]))
+            return false;
+
+        return $this->_aGroupsSettings[$iGroup];
+    }
+
+    public function getGroupCustom($aParams)
+    {
+        return $this->_oDb->getGroupCustom($aParams);
+    }
+
+    public function updateGroupCustom($aParamsSet, $aParamsWhere)
+    {
+        return $this->_oDb->updateGroupCustom($aParamsSet, $aParamsWhere);
+    }
+
+    public function deleteGroupCustom($aParamsWhere)
+    {
+        return $this->_oDb->deleteGroupCustom($aParamsWhere);
+    }
+
+    public function associateGroupCustomWithContent($iProfileId, $iContentId, $iGroupId)
+    {
+        return $this->updateGroupCustom(array('content_id' => $iContentId), array(
+            'profile_id' => $iProfileId,
+            'content_id' => 0,
+            'object' => $this->_sObject,
+            'group_id' => $iGroupId
+        ));
+    }
+
+    public function reassociateGroupCustomWithContent($iProfileId, $iContentId, $iGroupId)
+    {
+        $aGroupCustom = $this->getGroupCustom(array(
+            'type' => 'pco', 
+            'profile_id' => $iProfileId,
+            'content_id' => $iContentId,
+            'object' => $this->_sObject
+        ));
+
+        if(!empty($aGroupCustom) && is_array($aGroupCustom) && $aGroupCustom['group_id'] != $iGroupId)
+            $this->deleteGroupCustom(array('id' => $aGroupCustom['id']));
+
+        return $this->associateGroupCustomWithContent($iProfileId, $iContentId, $iGroupId);
+    }
+
+    public function deleteGroupCustomByContentId($iContentId)
+    {
+        return $this->_oDb->deleteGroupCustom(array('content_id' => $iContentId, 'object' => $this->_sObject));
+    }
+
+    public function deleteGroupCustomByProfileId($iProfileId)
+    {
+        $aGroups = $this->_oDb->getGroupCustom(array('type' => 'profile_id', 'profile_id' => $iProfileId));
+        if(empty($aGroups) || !is_array($aGroups))
+            return true;
+
+        foreach($aGroups as $aGroup)
+            $this->_oDb->deleteGroupCustom(array('id' => $aGroup['id']));
+
+        return true;
+    }
+
     public function addDynamicGroups($aValues, $iOwnerId, $aParams)
     {
         if (isset($aParams['dynamic_groups']) && is_array($aParams['dynamic_groups']))
@@ -324,7 +490,7 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
      * @param  integer $iViewerId viewer ID.
      * @return boolean result of operation.
      */
-    function check($iObjectId, $iViewerId = 0)
+    public function check($iObjectId, $iViewerId = 0)
     {
         if(empty($iViewerId))
             $iViewerId = (int)bx_get_logged_profile_id();
@@ -350,7 +516,7 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
         $aGroup = $this->_oDb->getGroupsBy(array('type' => 'id', 'id' => $aObject['group_id']));
         if(!empty($aGroup) && is_array($aGroup) && (int)$aGroup['active'] == 1 && !empty($aGroup['check'])) {
             $sCheckMethod = $this->getCheckMethod($aGroup['check']);
-            if(method_exists($this, $sCheckMethod) && $this->$sCheckMethod($aObject['owner_id'], $iViewerId))
+            if(method_exists($this, $sCheckMethod) && $this->$sCheckMethod((substr($sCheckMethod, -8) == 'ByObject' ? $aObject : $aObject['owner_id']), $iViewerId))
                 return true;
         }
 
@@ -386,6 +552,48 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
         return BxDolConnection::getObjectInstance('sys_profiles_friends')->isConnected($iOwnerId, $iViewerId, true);
     }
 
+    public function checkFriendsSelectedByObject($aObject, $iViewerId)
+    {
+        if(!$this->checkFriends($aObject['owner_id'], $iViewerId))
+            return false;
+
+        $aGroupCustom = $this->getGroupCustom(array(
+            'type' => 'pcog_ext', 
+            'profile_id' => $aObject['owner_id'], 
+            'content_id' => $aObject['id'], 
+            'object' => $this->_sObject, 
+            'group_id' => $aObject['group_id']
+        ));
+
+        return !empty($aGroupCustom['members']) && is_array($aGroupCustom['members']) && in_array($iViewerId, $aGroupCustom['members']);
+    }
+
+    public function checkRelations($iOwnerId, $iViewerId)
+    {
+        return BxDolConnection::getObjectInstance('sys_profiles_relations')->isConnected($aObject['owner_id'], $iViewerId, true);
+    }
+
+    public function checkRelationsSelectedByObject($aObject, $iViewerId)
+    {
+        if(!$this->checkRelations($aObject['owner_id'], $iViewerId))
+            return false;
+
+        $aGroupCustom = $this->getGroupCustom(array(
+            'type' => 'pcog_ext', 
+            'profile_id' => $aObject['owner_id'], 
+            'content_id' => $aObject['id'], 
+            'object' => $this->_sObject, 
+            'group_id' => $aObject['group_id']
+        ));
+
+        return !empty($aGroupCustom['members']) && is_array($aGroupCustom['members']) && in_array($iViewerId, $aGroupCustom['members']);
+    }
+
+    public function checkCustomByObject($aObject, $iViewerId)
+    {
+        return '';
+    }
+
     public function setTableFieldAuthor($sValue)
     {
         $this->_aObject['table_field_author'] = $sValue;
@@ -406,7 +614,7 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
             if($iProfileIdLogged == $iProfileIdOwner)
                 return true;
 
-			$aGroups[] = BX_DOL_PG_MEMBERS;
+            $aGroups[] = BX_DOL_PG_MEMBERS;
             if($iProfileIdOwner && $this->checkFriends($iProfileIdOwner, $iProfileIdLogged))
                 $aGroups[] = BX_DOL_PG_FRIENDS;
         }
@@ -455,6 +663,18 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
         }
         return $aValues;
     }
+
+    /**
+     * Check whethere custom group is allowed in current circumstances.
+     * NOTE. Can be overwritten if it's needed.
+     * 
+     * @param type $aParams an array of parameters.
+     * @return boolean result of operation.
+     */
+    protected function isSelectGroupCustom($aParams)
+    {
+        return true;
+    }
 }
 
 /** @} */

+ 135 - 0
inc/classes/BxDolPrivacyQuery.php

@@ -160,6 +160,141 @@ class BxDolPrivacyQuery extends BxDolDb
         return $this->query($sSql);
     }
 
+    public function getGroupCustom($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sDiv = ',';
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sSelectClause = "`tg`.*";
+    	$sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `tg`.`id`=:id";
+                break;
+
+            case 'id_ext':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sSelectClause .= ", GROUP_CONCAT(`tgm`.`member_id` SEPARATOR  '" . $sDiv . "') AS `members`";
+                $sJoinClause = "LEFT JOIN `sys_privacy_groups_custom_members` AS `tgm` ON `tg`.`id`=`tgm`.`group_id`";
+                $sWhereClause = " AND `tg`.`id`=:id";
+                $sGroupClause = "`tg`.`id`";
+                break;
+
+            case 'pco':
+                $aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'profile_id' => $aParams['profile_id'],
+                    'content_id' => $aParams['content_id'],
+                    'object' => $aParams['object'],
+                );
+
+                $sWhereClause = " AND `tg`.`profile_id`=:profile_id AND `tg`.`content_id`=:content_id AND `tg`.`object`=:object";
+                break;
+
+            case 'pcog_ext':
+                $aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'profile_id' => $aParams['profile_id'],
+                    'content_id' => $aParams['content_id'],
+                    'object' => $aParams['object'],
+                    'group_id' => $aParams['group_id'],
+                );
+
+                $sSelectClause .= ", GROUP_CONCAT(`tgm`.`member_id` SEPARATOR  '" . $sDiv . "') AS `members`";
+                $sJoinClause = "LEFT JOIN `sys_privacy_groups_custom_members` AS `tgm` ON `tg`.`id`=`tgm`.`group_id`";
+                $sWhereClause = " AND `tg`.`profile_id`=:profile_id AND (`tg`.`content_id`=:content_id" . (!empty($aParams['content_id']) ? " OR `tg`.`content_id`='0'" : "") . ") AND `tg`.`object`=:object AND `tg`.`group_id`=:group_id";
+                $sGroupClause = "`tg`.`id`";
+                break;
+
+            case 'profile_id':
+            	$aMethod['params'][1] = array(
+                    'profile_id' => $aParams['profile_id']
+                );
+
+                $sWhereClause = " AND `tg`.`profile_id`=:profile_id";
+                break;
+        }
+
+        $sGroupClause = !empty($sGroupClause) ? "GROUP BY " . $sGroupClause : $sGroupClause;
+        $sOrderClause = !empty($sOrderClause) ? "ORDER BY " . $sOrderClause : $sOrderClause;
+        $sLimitClause = !empty($sLimitClause) ? "LIMIT " . $sLimitClause : $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `sys_privacy_groups_custom` AS `tg` " . $sJoinClause . " 
+            WHERE 1" . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+
+        $aResult = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+        if(in_array($aParams['type'], array('id_ext', 'pcog_ext')) && !empty($aResult) && is_array($aResult))
+            $aResult['members'] = explode($sDiv, $aResult['members']);
+
+        return $aResult;
+    }
+
+    public function insertGroupCustom($aParamsSet)
+    {
+        if(empty($aParamsSet))
+            return false;
+
+        if(!$this->query("INSERT INTO `sys_privacy_groups_custom` SET " . $this->arrayToSQL($aParamsSet)))
+            return false;
+
+        return (int)$this->lastId();
+    }
+
+    public function updateGroupCustom($aParamsSet, $aParamsWhere)
+    {
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        return $this->query("UPDATE `sys_privacy_groups_custom` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function deleteGroupCustom($aParamsWhere)
+    {
+        if(empty($aParamsWhere))
+            return false;
+
+        $sWhereClause = $this->arrayToSQL($aParamsWhere, " AND ");
+
+        $aGroup = $this->getRow("SELECT * FROM `sys_privacy_groups_custom` WHERE " . $sWhereClause . " LIMIT 1");
+        if(empty($aGroup) || !is_array($aGroup))
+            return true;
+
+        $bResult = $this->query("DELETE FROM `sys_privacy_groups_custom` WHERE " . $sWhereClause . " LIMIT 1") !== false;
+        if($bResult)
+            $this->deleteGroupCustomMember(array('group_id' => $aGroup['id']));
+
+        return $bResult;
+    }
+
+    public function insertGroupCustomMember($aParamsSet)
+    {
+        if(empty($aParamsSet))
+            return false;
+
+        return $this->query("INSERT INTO `sys_privacy_groups_custom_members` SET " . $this->arrayToSQL($aParamsSet));
+    }
+
+    public function deleteGroupCustomMember($aParamsWhere)
+    {
+        if(empty($aParamsWhere))
+            return false;
+
+        return $this->query("DELETE FROM `sys_privacy_groups_custom_members` WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
     function getContentByGroupAsSQLPart($sField, $mixedGroupId)
     {
         if(is_array($mixedGroupId))

+ 155 - 0
inc/js/classes/BxDolPrivacy.js

@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+function BxDolPrivacy(oOptions) {
+    this.BX_DOL_PG_FRIENDS_SELECTED = 6;
+    this.BX_DOL_PG_RELATIONS_SELECTED = 8;
+
+    this._sObject = oOptions.sObject;
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxDolPrivacy' : oOptions.sObjName;
+
+    this._sRootUrl = oOptions.sRootUrl == undefined ? sUrlRoot : oOptions.sRootUrl;
+    this._sActionsUri = 'privacy.php';
+    this._sActionsUrl = this._sRootUrl + this._sActionsUri; // actions url address
+
+    this._aGroupSettings = oOptions.aGroupSettings == undefined ? {} : oOptions.aGroupSettings;
+
+    this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
+    this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+    this._aHtmlIds = oOptions.aHtmlIds == undefined ? {} : oOptions.aHtmlIds;
+
+    this._iProfileId = 0;
+    this._iContentId = 0;
+}
+
+BxDolPrivacy.prototype.loadGroupCustom = function(aParams)
+{
+    var $this = this;
+
+    aParams = aParams || {};
+    if(aParams && aParams.profile_id != undefined)
+        this._iProfileId = parseInt(aParams.profile_id);
+
+    if(aParams && aParams.content_id != undefined)
+        this._iContentId = parseInt(aParams.content_id);
+
+    $(document).ready(function() {
+        var oElement = $this._getSelectGroupElement();
+
+        if(!aParams.group_id && oElement && oElement.length > 0)
+            aParams.group_id = parseInt($(oElement).val());
+
+        if(!aParams.group_id || !$this._aGroupSettings[aParams.group_id])
+            return;
+
+        var oData = $.extend({}, $this._getDefaultParams(), {action: 'load_group_custom'}, aParams);
+
+        $this._loadingInFormElement(oElement, true);
+
+        $.get(
+            $this._sActionsUrl, 
+            oData,
+            function(oData) {
+                $this._loadingInFormElement(oElement, false);
+
+                processJsonData(oData);
+            }, 
+            'json'
+        );
+    });
+};
+
+BxDolPrivacy.prototype.selectGroup = function(oElement)
+{
+    $('#' + this._aHtmlIds['group_custom_element']).remove();
+
+    var iGroupId = parseInt($(oElement).val());
+    if(!this._aGroupSettings[iGroupId])
+        return;
+
+    this.selectUsers(oElement, iGroupId);
+};
+
+BxDolPrivacy.prototype.onSelectGroup = function(oData)
+{
+    var oElement = this._getSelectGroupElement();
+
+    if(oData && oData.content != undefined && oElement.length > 0)
+        oElement.after(oData.content);
+};
+
+BxDolPrivacy.prototype.editGroup = function(oElement)
+{
+    var iGroupId = parseInt($(oElement).parents('.bx-form-element:first').find('.sys-privacy-group').val());
+
+    this.selectUsers(oElement, iGroupId, {
+        popup_only: 1
+    });
+};
+
+BxDolPrivacy.prototype.selectUsers = function(oElement, iGroupId, aParams)
+{
+    var $this = this;
+    var oData = $.extend({}, $this._getDefaultParams(), {action: 'select_group', group_id: iGroupId}, (aParams || {}));
+
+    this._loadingInFormElement(oElement, true);
+
+    $.get(
+        this._sActionsUrl, 
+        oData,
+        function(oData) {
+            $this._loadingInFormElement(oElement, false);
+
+            processJsonData(oData);
+        }, 
+        'json'
+    );
+};
+
+BxDolPrivacy.prototype.onSelectUsers = function(oData)
+{
+    $('#' + this._aHtmlIds['group_custom_element']).remove();
+
+    this.onSelectGroup(oData);
+};
+
+BxDolPrivacy.prototype._loading = function(e, bShow)
+{
+    var oParent = $(e).length ? $(e) : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxDolPrivacy.prototype._loadingInBlock = function(e, bShow)
+{
+    var oParent = $(e).length ? $(e).parents('.bx-db-content:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxDolPrivacy.prototype._loadingInFormElement = function(e, bShow)
+{
+    var oParent = $(e).length ? $(e).parents('.bx-form-element:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxDolPrivacy.prototype._getSelectGroupElement = function()
+{
+    return $('#' + this._aHtmlIds['group_element'] + ' .sys-privacy-group');
+};
+
+BxDolPrivacy.prototype._getDefaultParams = function() 
+{
+    var oDate = new Date();
+    return {
+        profile_id: this._iProfileId,
+        content_id: this._iContentId,
+        object: this._sObject,
+        _t: oDate.getTime()
+    };
+};
+
+/** @} */

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
install/sql/system.sql


+ 8 - 0
modules/base/general/classes/BxBaseModGeneralFormEntry.php

@@ -149,6 +149,14 @@ class BxBaseModGeneralFormEntry extends BxTemplFormView
             $this->aInputs[$CNF['FIELD_ANONYMOUS']]['checked'] = $aValues[$CNF['FIELD_AUTHOR']] < 0;
         }
 
+        if (isset($CNF['FIELD_ALLOW_VIEW_TO']) && isset($this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']]) && $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) {
+            $iContentId = !empty($aValues[$CNF['FIELD_ID']]) ? (int)$aValues[$CNF['FIELD_ID']] : 0;
+            $iProfileId = !empty($iContentId) ? (int)$this->getContentOwnerProfileId($iContentId) : bx_get_logged_profile_id();
+            $iGroupId = !empty($aValues[$CNF['FIELD_ALLOW_VIEW_TO']]) ? $aValues[$CNF['FIELD_ALLOW_VIEW_TO']] : 0;
+
+            $oPrivacy->loadGroupCustom($iProfileId, $iContentId, $iGroupId);
+        }
+
         parent::initChecker ($aValues, $aSpecificValues);
     }
     

+ 16 - 0
modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php

@@ -474,6 +474,11 @@ class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
 
     public function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
     {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->deleteGroupCustomByContentId($iContentId);
+
         return '';
     }
 
@@ -489,6 +494,9 @@ class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
     {
         $CNF = &$this->_oModule->_oConfig->CNF;
 
+        /*
+         * Load update data.
+         */
         list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
 
         if(isset($CNF['FIELD_PHOTO']))
@@ -498,6 +506,9 @@ class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
             if($aTrackResult['old'] == 'failed' && $aTrackResult['new'] == 'active')
                 $this->_oModule->alertAfterAdd($aContentInfo);
 
+        if(isset($CNF['FIELD_ALLOW_VIEW_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->reassociateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
         return '';
     }
 
@@ -505,11 +516,16 @@ class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
     {
         $CNF = &$this->_oModule->_oConfig->CNF;
 
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
         if(($oForm = $this->getObjectFormAdd()) !== false) {
             if(isset($CNF['FIELD_PHOTO']))
                 $oForm->processFiles($CNF['FIELD_PHOTO'], $iContentId, true);
         }
 
+        if(isset($CNF['FIELD_ALLOW_VIEW_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->associateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
         return '';
     }
 

+ 3 - 2
modules/base/general/classes/BxBaseModGeneralModule.php

@@ -1224,8 +1224,9 @@ class BxBaseModGeneralModule extends BxDolModule
 
         // file owner must be author of the content or profile itself in case of profile based module
         if ($iContentId) {
-            if ($this instanceof iBxDolProfileService) {
-                $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+            $sModule = $this->getName();
+            if ($this instanceof iBxDolProfileService && BxDolService::call($sModule, 'act_as_profile')) {
+                $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $sModule);
             }
             else {
                 $aContentInfo = $this->_oDb->getContentInfoById($iContentId);

+ 18 - 0
modules/base/groups/classes/BxBaseModGroupsFormsEntryHelper.php

@@ -19,6 +19,18 @@ class BxBaseModGroupsFormsEntryHelper extends BxBaseModProfileFormsEntryHelper
         parent::__construct($oModule);
     }
 
+    protected function _getProfileAndContentData ($iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+        if(!$aContentInfo)
+            return array(false, false);
+
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo[$CNF['FIELD_AUTHOR']]);
+        return array($oProfile, $aContentInfo);
+    }
+
     protected function _processPermissionsCheckForViewDataForm ($aContentInfo, $oProfile)
     {
         $sMsg = parent::_processPermissionsCheckForViewDataForm ($aContentInfo, $oProfile);
@@ -69,6 +81,12 @@ class BxBaseModGroupsFormsEntryHelper extends BxBaseModProfileFormsEntryHelper
         if (isset($CNF['OBJECT_CONNECTIONS']) && $oGroupProfile && ($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
             $oConnection->onDeleteInitiatorAndContent($oGroupProfile->id());
 
+        if(($oPrivacyView = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacyView->deleteGroupCustomByContentId($iContentId);
+
+        if(($oPrivacyPost = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
+            $oPrivacyPost->deleteGroupCustomByContentId($iContentId);
+
         return '';
     }
 

+ 8 - 0
modules/base/profile/classes/BxBaseModProfileFormEntry.php

@@ -81,6 +81,14 @@ class BxBaseModProfileFormEntry extends BxBaseModGeneralFormEntry
             $this->aInputs[$sField]['ghost_template'] = $this->_oModule->_oTemplate->parseHtmlByName('form_ghost_template.html', $this->_getProfilePhotoGhostTmplVars($sField, $aContentInfo));
         }
 
+        if (isset($CNF['FIELD_ALLOW_POST_TO']) && isset($this->aInputs[$CNF['FIELD_ALLOW_POST_TO']]) && $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) {
+            $iContentId = !empty($aValues[$CNF['FIELD_ID']]) ? (int)$aValues[$CNF['FIELD_ID']] : 0;
+            $iProfileId = !empty($iContentId) ? (int)$this->getContentOwnerProfileId($iContentId) : bx_get_logged_profile_id();
+            $iGroupId = !empty($aValues[$CNF['FIELD_ALLOW_POST_TO']]) ? $aValues[$CNF['FIELD_ALLOW_POST_TO']] : 0;
+
+            $oPrivacy->loadGroupCustom($iProfileId, $iContentId, $iGroupId);
+        }
+
         parent::initChecker($aValues, $aSpecificValues);
     }
 

+ 48 - 16
modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php

@@ -39,18 +39,12 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
 
     protected function _getProfileAndContentData ($iContentId)
     {
-        $aContentInfo = array();
-        $oProfile = false;
-
         $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
-        if (!$aContentInfo)
+        if(!$aContentInfo)
             return array (false, false);
 
-        $oProfile = BxDolProfile::getInstance($aContentInfo['profile_id']);
-        if (!$oProfile) 
-            $oProfile = BxDolProfileUndefined::getInstance();
-
-        return array ($oProfile, $aContentInfo);
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo['profile_id']);
+        return array($oProfile, $aContentInfo);
     }
 
     public function deleteData ($iContentId, $aContentInfo = false, $oProfile = null, $oForm = null)
@@ -71,6 +65,27 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
         return parent::deleteData ($iContentId, $aContentInfo, $oProfile, $oForm);
     }
 
+    public function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
+    {
+        if($s = parent::onDataDeleteAfter($iContentId, $aContentInfo, $oProfile))
+            return $s;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bActAsProfile = BxDolService::call($oProfile->getModule(), 'act_as_profile');
+        if(($oPrivacyView = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false && $bActAsProfile)
+            $oPrivacyView->deleteGroupCustomByProfileId($oProfile->id());
+
+        if(($oPrivacyPost = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false) {
+            $oPrivacyPost->deleteGroupCustomByContentId($iContentId);
+
+            if($bActAsProfile)
+                $oPrivacyPost->deleteGroupCustomByProfileId($oProfile->id());
+        }
+
+        return '';
+    }
+
     public function onDataEditBefore ($iContentId, $aContentInfo, &$aTrackTextFieldsChanges, &$oProfile, &$oForm)
     {
         $CNF = &$this->_oModule->_oConfig->CNF;
@@ -89,6 +104,11 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
 
         $CNF = &$this->_oModule->_oConfig->CNF;
 
+        /*
+         * Load update data.
+         */
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
         $aProfileInfo = $oProfile->getInfo();
 
         // change profile to 'pending' only if profile is 'active'
@@ -101,6 +121,9 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
         if (isset($CNF['FIELD_COVER']))
             $oForm->processFiles($CNF['FIELD_COVER'], $iContentId, false);
 
+        if(isset($CNF['FIELD_ALLOW_POST_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
+            $oPrivacy->reassociateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]);
+
         // create an alert
         bx_alert($this->_oModule->getName(), 'edited', $aContentInfo[$CNF['FIELD_ID']]);
         bx_alert('profile', 'edit', $oProfile->id(), 0, array('content' => $iContentId, 'module' => $this->_oModule->getName()));
@@ -110,14 +133,21 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
 
     public function onDataAddAfter ($iAccountId, $iContentId)
     {
-        if ($s = parent::onDataAddAfter($iAccountId, $iContentId))
+        /*
+         * Add account and content association.
+         * Note. It should be done first to correctly get and user author's profile later.
+         */
+        $iProfileId = BxDolProfile::add(BX_PROFILE_ACTION_MANUAL, $iAccountId, $iContentId, BX_PROFILE_STATUS_PENDING, $this->_oModule->getName());
+
+        if($s = parent::onDataAddAfter($iAccountId, $iContentId))
             return $s;
 
         $CNF = &$this->_oModule->_oConfig->CNF;
-
-        // add account and content association
-        $iProfileId = BxDolProfile::add(BX_PROFILE_ACTION_MANUAL, $iAccountId, $iContentId, BX_PROFILE_STATUS_PENDING, $this->_oModule->getName());
-        $oProfile = BxDolProfile::getInstance($iProfileId);
+        
+        /*
+         * Load update data.
+         */
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
 
         // approve profile if auto-approval is enabled and profile status is 'pending'
         $sStatus = $oProfile->getStatus();
@@ -139,12 +169,14 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
         if (isset($CNF['FIELD_COVER']))
             $oForm->processFiles($CNF['FIELD_COVER'], $iContentId, true);
 
+        if(isset($CNF['FIELD_ALLOW_POST_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
+            $oPrivacy->associateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]);
+
         // alert
-        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
         $aParams = array();        
         if(isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))
             $aParams['privacy_view'] = $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']];
-        
+
         bx_alert($this->_oModule->getName(), 'added', $iContentId, false, $aParams);
 
         // switch context to the created profile

+ 8 - 0
modules/base/profile/classes/BxBaseModProfilePrivacy.php

@@ -46,6 +46,14 @@ class BxBaseModProfilePrivacy extends BxTemplPrivacy
 
         return $a;
     }
+
+    protected function isSelectGroupCustom($aParams)
+    {
+        if($this->_oModule->serviceActAsProfile() && empty($aParams['content_id']))
+            return _t('_sys_ps_ferr_incorrect_gc_not_allowed');
+
+        return true;
+    }
 }
 
 /** @} */

+ 8 - 0
modules/base/profile/classes/BxBaseModProfilePrivacyPost.php

@@ -32,6 +32,14 @@ class BxBaseModProfilePrivacyPost extends BxTemplPrivacy
 
         return $a;
     }
+
+    protected function isSelectGroupCustom($aParams)
+    {
+        if($this->_oModule->serviceActAsProfile() && empty($aParams['content_id']))
+            return _t('_sys_ps_ferr_incorrect_gc_not_allowed');
+
+        return true;
+    }
 }
 
 /** @} */

+ 4 - 10
modules/base/text/classes/BxBaseModTextFormsEntryHelper.php

@@ -45,18 +45,12 @@ class BxBaseModTextFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
     {
         $CNF = &$this->_oModule->_oConfig->CNF;
 
-        $aContentInfo = array();
-        $oProfile = false;
-
         $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
-        if (!$aContentInfo)
-            return array (false, false);
-
-        $oProfile = BxDolProfile::getInstance($aContentInfo[$CNF['FIELD_AUTHOR']]);
-        if (!$oProfile) 
-            $oProfile = BxDolProfileUndefined::getInstance();
+        if(!$aContentInfo)
+            return array(false, false);
 
-        return array ($oProfile, $aContentInfo);
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo[$CNF['FIELD_AUTHOR']]);
+        return array($oProfile, $aContentInfo);
     }
 
     public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)

+ 21 - 0
modules/boonex/decorous/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.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 BxBasePrivacyFormUsers
+ */
+class BxTemplPrivacyFormGroupCustom extends BxBasePrivacyFormGroupCustom
+{
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+    }
+}
+
+/** @} */

+ 21 - 2
modules/boonex/english/data/langs/system/en.xml

@@ -844,7 +844,6 @@
 	<string name="_adm_form_txt_field_type_text"><![CDATA[text]]></string>
 	<string name="_adm_form_txt_field_type_text_auto"><![CDATA[auto text]]></string>
 	<string name="_adm_form_txt_field_type_textarea"><![CDATA[textarea]]></string>
-  <string name="_adm_form_txt_field_type_time"><![CDATA[time]]></string>
 	<string name="_adm_form_txt_field_type_value"><![CDATA[info]]></string>
 	<string name="_adm_form_txt_field_unique"><![CDATA[Unique]]></string>
 	<string name="_adm_form_txt_field_value_button"><![CDATA[Title]]></string>
@@ -2096,8 +2095,22 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_form_login_input_phone_error_format"><![CDATA[Wrong phone number, allowed digits only and start from '+']]></string>
 	<string name="_sys_form_login_input_phone_error_not_unique"><![CDATA[This phone is already used by someone.]]></string>
 	<string name="_sys_form_login_input_code_error_invalid"><![CDATA[Wrong verification code. Please, repeat code or <a href="{0}">change the phone number and repeat from the beginning</a>.]]></string>
-	
 	<string name="_sys_form_paceholder_profiles_suggestions"><![CDATA[Type name here...]]></string>
+        <string name="_sys_form_ps_group_custom"><![CDATA[Privacy: Manage custom group]]></string>
+        <string name="_sys_form_display_ps_gc_select"><![CDATA[Privacy: Select custom group display]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_profile_id"><![CDATA[Profile ID]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_content_id"><![CDATA[Content ID]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_object"><![CDATA[Object]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_action"><![CDATA[Action]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_group_id"><![CDATA[Group ID]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_search"><![CDATA[Search]]></string>
+        <string name="_sys_form_ps_gc_input_caption_search"><![CDATA[Search]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_list"><![CDATA[List]]></string>
+        <string name="_sys_form_ps_gc_input_caption_list"><![CDATA[or select]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_do_submit"><![CDATA[Submit]]></string>
+        <string name="_sys_form_ps_gc_input_caption_do_submit"><![CDATA[Submit]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_do_cancel"><![CDATA[Cancel]]></string>
+        <string name="_sys_form_ps_gc_input_caption_do_cancel"><![CDATA[Cancel]]></string>
 	<string name="_sys_form_report"><![CDATA[Report]]></string>
 	<string name="_sys_form_report_input_caption_submit"><![CDATA[Post]]></string>
 	<string name="_sys_form_report_input_caption_text"><![CDATA[Report Text]]></string>
@@ -2630,14 +2643,20 @@ If it is not enabled then please consider implement this optimization, since it
             <li><b>{0}</b> total</li>
             <li><b>{1}</b> deleted</li>
         </ul>]]></string>
+        <string name="_sys_ps_group_title_custom"><![CDATA[Custom]]></string>
         <string name="_sys_ps_group_title_followers"><![CDATA[Followers]]></string>
 	<string name="_sys_ps_group_title_friends"><![CDATA[Friends]]></string>
+        <string name="_sys_ps_group_title_friends_selected"><![CDATA[Selected Friends]]></string>
+        <string name="_sys_ps_group_title_relations"><![CDATA[Relationships]]></string>
+        <string name="_sys_ps_group_title_relations_selected"><![CDATA[Selected Relationships]]></string>
 	<string name="_sys_ps_group_title_me_only"><![CDATA[Me only]]></string>
 	<string name="_sys_ps_group_title_members"><![CDATA[Members]]></string>
 	<string name="_sys_ps_group_title_public"><![CDATA[Public]]></string>
         <string name="_sys_ps_group_title_participants"><![CDATA[Participants]]></string>
 	<string name="_sys_ps_group_title_closed"><![CDATA[Closed]]></string>
 	<string name="_sys_ps_group_title_secret"><![CDATA[Secret]]></string>
+    <string name="_sys_ps_ferr_incorrect_gc_not_allowed"><![CDATA[Custom privacy groups aren't allowed.]]></string>
+    <string name="_sys_ps_ferr_incorrect_gc_owner"><![CDATA[You are not an owner of the content.]]></string>
     <string name="_sys_ps_ferr_incorrect_select"><![CDATA[You need to select some value]]></string>
     <string name="_sys_ps_space_title_friend"><![CDATA[friend]]></string>
     <string name="_sys_push_notification_request"><![CDATA[Would you like to get notifications from {0}?]]></string>  

+ 21 - 0
modules/boonex/lucid/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.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 BxBasePrivacyFormUsers
+ */
+class BxTemplPrivacyFormGroupCustom extends BxBasePrivacyFormGroupCustom
+{
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+    }
+}
+
+/** @} */

+ 21 - 0
modules/boonex/ocean/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.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 BxBasePrivacyFormUsers
+ */
+class BxTemplPrivacyFormGroupCustom extends BxBasePrivacyFormGroupCustom
+{
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+    }
+}
+
+/** @} */

+ 21 - 0
modules/boonex/protean/data/template/system/scripts/BxTemplPrivacyFormGroupCustom.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 BxBasePrivacyFormUsers
+ */
+class BxTemplPrivacyFormGroupCustom extends BxBasePrivacyFormGroupCustom
+{
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+    }
+}
+
+/** @} */

+ 23 - 4
modules/boonex/russian/data/langs/system/ru.xml

@@ -844,7 +844,6 @@
 	<string name="_adm_form_txt_field_type_text"><![CDATA[текст]]></string>
 	<string name="_adm_form_txt_field_type_text_auto"><![CDATA[авто текст]]></string>
 	<string name="_adm_form_txt_field_type_textarea"><![CDATA[текстовое поле]]></string>
-  <string name="_adm_form_txt_field_type_time"><![CDATA[время]]></string>
 	<string name="_adm_form_txt_field_type_value"><![CDATA[инфо]]></string>
 	<string name="_adm_form_txt_field_unique"><![CDATA[Уникальное]]></string>
 	<string name="_adm_form_txt_field_value_button"><![CDATA[Заголовок]]></string>
@@ -2097,8 +2096,22 @@
 	<string name="_sys_form_login_input_phone_error_format"><![CDATA[Телефон должен содержать только цифры и начинаться с '+']]></string>
 	<string name="_sys_form_login_input_phone_error_not_unique"><![CDATA[Этот телефон уже кем-то используется]]></string>
 	<string name="_sys_form_login_input_code_error_invalid"><![CDATA[Неправильный код проверки. Пожалуйста, повторите код или  <a href="{0}">измените телефонный номер и начните сначала</a>.]]></string>
-	
 	<string name="_sys_form_paceholder_profiles_suggestions"><![CDATA[Введите имя...]]></string>
+        <string name="_sys_form_ps_group_custom"><![CDATA[Конфиденциальность: Пользовательские группы]]></string>
+        <string name="_sys_form_display_ps_gc_select"><![CDATA[Конфиденциальность: Выбрать пользовательскую группу]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_profile_id"><![CDATA[ID профиля]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_content_id"><![CDATA[ID контента]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_object"><![CDATA[Объект]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_action"><![CDATA[Действие]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_group_id"><![CDATA[ID группы]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_search"><![CDATA[Поиск]]></string>
+        <string name="_sys_form_ps_gc_input_caption_search"><![CDATA[Найти]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_list"><![CDATA[Список]]></string>
+        <string name="_sys_form_ps_gc_input_caption_list"><![CDATA[или выбрать]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_do_submit"><![CDATA[Отправить]]></string>
+        <string name="_sys_form_ps_gc_input_caption_do_submit"><![CDATA[Отправить]]></string>
+        <string name="_sys_form_ps_gc_input_caption_system_do_cancel"><![CDATA[Отменить]]></string>
+        <string name="_sys_form_ps_gc_input_caption_do_cancel"><![CDATA[Отменить]]></string>
 	<string name="_sys_form_report"><![CDATA[Пожаловаться]]></string>
 	<string name="_sys_form_report_input_caption_submit"><![CDATA[Пожаловаться]]></string>
 	<string name="_sys_form_report_input_caption_text"><![CDATA[Текст жалобы]]></string>
@@ -2629,14 +2642,20 @@
             <li><b>{0}</b> всего</li>
             <li><b>{1}</b> удалено</li>
         </ul>]]></string>
+        <string name="_sys_ps_group_title_custom"><![CDATA[Индивидуально]]></string>
         <string name="_sys_ps_group_title_followers"><![CDATA[Подписчики]]></string>
 	<string name="_sys_ps_group_title_friends"><![CDATA[Друзья]]></string>
+        <string name="_sys_ps_group_title_friends_selected"><![CDATA[Друзья (выборочно)]]></string>
+        <string name="_sys_ps_group_title_relations"><![CDATA[Отношения]]></string>
+        <string name="_sys_ps_group_title_relations_selected"><![CDATA[Отношения (выборочно)]]></string>
 	<string name="_sys_ps_group_title_me_only"><![CDATA[Только я]]></string>
-	<string name="_sys_ps_group_title_members"><![CDATA[Пользователей]]></string>
-	<string name="_sys_ps_group_title_public"><![CDATA[Публичные]]></string>
+	<string name="_sys_ps_group_title_members"><![CDATA[Пользователи]]></string>
+	<string name="_sys_ps_group_title_public"><![CDATA[Все]]></string>
         <string name="_sys_ps_group_title_participants"><![CDATA[Участники]]></string>
 	<string name="_sys_ps_group_title_closed"><![CDATA[Закрытая]]></string>
     <string name="_sys_ps_group_title_secret"><![CDATA[Секретная]]></string>
+    <string name="_sys_ps_ferr_incorrect_gc_not_allowed"><![CDATA[Пользовательские группы конфиденциальности не доступны.]]></string>
+    <string name="_sys_ps_ferr_incorrect_gc_owner"><![CDATA[Вы не являетесь владельцем данного контента.]]></string>
     <string name="_sys_ps_ferr_incorrect_select"><![CDATA[Вам нужно выбрать значение]]></string>
     <string name="_sys_ps_space_title_friend"><![CDATA[друг]]></string>
     <string name="_sys_push_notification_request"><![CDATA[Хотите получать уведомления с сайта {0}?]]></string>   

+ 29 - 0
privacy.php

@@ -0,0 +1,29 @@
+<?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");
+
+bx_import('BxDolLanguages');
+
+check_logged();
+
+$sObject = isset($_REQUEST['object']) ? bx_process_input($_REQUEST['object']) : false;
+$sAction = isset($_REQUEST['action']) && preg_match ('/^[A-Za-z_-]+$/', $_REQUEST['action']) ? bx_process_input($_REQUEST['action']) : '';
+
+$oPrivacy = BxDolPrivacy::getObjectInstance($sObject);
+
+if($oPrivacy && $sObject && $sAction) {
+    header('Content-Type: text/html; charset=utf-8');
+    $sMethod = 'action' . bx_gen_method_name($sAction);
+    if(method_exists($oPrivacy, $sMethod))
+        echo $oPrivacy->$sMethod();
+}
+
+/** @} */

+ 12 - 4
template/css/forms.css

@@ -114,9 +114,11 @@
 
 /* form inputs */
 .bx-form-input-wrapper {
-	position: relative;
+    position: relative;
 }
 
+.bx-form-input-disabled {}
+
 .bx-form-input-slider,
 .bx-form-input-doublerange,
 .bx-form-input-select_multiple,
@@ -530,7 +532,7 @@
     opacity:0.7;
 }
 .bx-form-value .bx-form-input-autotoken b:after {
-	font-family: 'sys-user', 'sys-user-fal';
+    font-family: 'sys-user', 'sys-user-fal';
     content: "\f00d";    
     position:absolute;    
     padding:0 0.5em;
@@ -538,13 +540,19 @@
     font-size:0.7em;
     font-weight:bold;
 }
+.bx-form-value .bx-form-input-autotoken.bx-form-input-disabled b {
+    cursor: default;
+}
+.bx-form-value .bx-form-input-autotoken.bx-form-input-disabled b:after{
+    display: none;
+}
 .bx-form-value .bx-form-input-autotoken input {
     border:0;
     margin:0;
     padding:0 0 0 5px;
     width:auto;
-	float:left;
-	box-shadow: none;
+    float:left;
+    box-shadow: none;
 }
 .bx-form-value .bx-form-input-autotoken .bx-def-thumb-size {
     width: 2.5rem;

+ 22 - 0
template/css/privacy.css

@@ -0,0 +1,22 @@
+/*--- Users Select Popup ---*/
+.ui-autocomplete.ui-front {
+    z-index: 1001;
+}
+
+.sys-privacy-us-list {
+    max-height: 200px;
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+.sys-privacy-usl-item {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+.sys-privacy-usl-unit .bx-base-pofile-unit {
+    margin: 0;
+}

+ 14 - 9
template/form_field_custom_suggestions.html

@@ -1,12 +1,17 @@
-<div id="__id__" class="bx-form-input-autotoken bx-def-font-inputs bx-form-input-text">
+<div id="__id__" class="__class__">
     __vals__
-    <input type="text" value="" class="bx-def-font-inputs bx-form-input-text" autocomplete="off" autocapitalize="off" autocorrect="off" />
+    <bx_if:input>
+        <input type="text" class="bx-def-font-inputs bx-form-input-text" __attrs__ />
+    </bx_if:input>
 </div>
-<script>
-	$(function(){
-        $('#__id__ input').on('focus', function () {
-            $(this).attr("placeholder", "__placeholder__");
+<bx_if:init>
+    <script type="text/javascript" language="javascript">
+        $(function(){
+            $('#__id__ input').on('focus', function () {
+                $(this).attr("placeholder", "__placeholder__");
+            });
+
+            bx_autocomplete_fields('__id__','__url_get_recipients__','__name__',__b_img__,__only_once__,__on_select__);
         });
-		bx_autocomplete_fields('__id__','__url_get_recipients__','__name__',__b_img__,__only_once__,__on_select__);
-	});
-</script>
+    </script>
+</bx_if:init>

+ 1 - 0
template/privacy_group_custom.html

@@ -0,0 +1 @@
+<div id="__html_id__" class="sys-privacy-group-custom bx-def-margin-top" onclick="javascript:__js_object__.editGroup(this)">__input_id____input_members__</div>

+ 8 - 0
template/privacy_users_select_list.html

@@ -0,0 +1,8 @@
+<div class="sys-privacy-us-list">
+    <bx_repeat:users>
+        <div class="sys-privacy-usl-item bx-def-padding-thd-topbottom bx-clearfix">
+            <div class="sys-privacy-usl-checbox">__checkbox__</div>
+            <div class="sys-privacy-usl-unit bx-def-margin-left">__unit__</div>            
+        </div>
+    </bx_repeat:users>
+</div>

+ 20 - 0
template/privacy_users_select_popup.html

@@ -0,0 +1,20 @@
+<div class="sys-privacy-users-select sys-privacy-users-select-__group__ bx-popup-width">
+    <div class="sys-privacy-users-select-cnt bx-def-padding">
+        __form__
+        <script type="text/javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (oData) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+
+                        processJsonData(oData);
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>

+ 39 - 9
template/scripts/BxBaseFormView.php

@@ -1112,24 +1112,54 @@ BLAH;
         $this->addCssJsUi();
 
         $sVals = '';
-        if (!empty($aInput['value']) && is_array($aInput['value'])) {
-            foreach ($aInput['value'] as $sVal) {
-                if (!$sVal || !($oProfile = BxDolProfile::getInstance($sVal)))
+        if(!empty($aInput['value']) && is_array($aInput['value'])) {
+            foreach($aInput['value'] as $sVal) {
+                if(!$sVal || !($oProfile = BxDolProfile::getInstance($sVal)))
                     continue;
+
                $sVals .= '<b class="val bx-def-color-bg-hl bx-def-round-corners">' . $oProfile->getUnit(0, array('template' => 'unit_wo_info')) . $oProfile->getDisplayName() . '<input type="hidden" name="' . $aInput['name'] . '[]" value="' . $sVal . '" /></b>';
             }
             $sVals = trim($sVals, ',');
         }
 
+        $bDisabled = isset($aInput['attrs']['disabled']) && $aInput['attrs']['disabled'] == 'disabled';
+
+        $sId = $aInput['name'] . time() . mt_rand(0, 100);
+        $sClass = 'bx-form-input-autotoken bx-def-font-inputs bx-form-input-text';
+        if($bDisabled)
+            $sClass .= ' bx-form-input-disabled';
+        if(!empty($aInput['attrs']['class'])) {
+            $sClass .= ' ' . $aInput['attrs']['class'];
+            unset($aInput['attrs']['class']);
+        }
+
+        $aAttrs = array('value' => '', 'autocomplete' => 'off', 'autocapitalize' => 'off', 'autocorrect' => 'off');
+        if(isset($aInput['attrs']) && is_array($aInput['attrs']))
+            $aAttrs = array_merge($aAttrs, $aInput['attrs']);
+
         return $this->oTemplate->parseHtmlByName('form_field_custom_suggestions.html', array(
-            'id' => $aInput['name'] . time() . mt_rand(0, 100),
-            'url_get_recipients' => $aInput['ajax_get_suggestions'],
+            'id' => $sId,
             'name' => $aInput['name'],
-			'b_img' => isset($aInput['custom']['b_img']) ? (int)$aInput['custom']['b_img'] : 1,
-			'only_once' => isset($aInput['custom']['only_once']) ? 1 : 0,
-			'on_select' => isset($aInput['custom']['on_select']) ? $aInput['custom']['on_select']: 'null',
-            'placeholder' => bx_html_attribute(isset($aInput['placeholder']) ? $aInput['placeholder'] : _t('_sys_form_paceholder_profiles_suggestions'), BX_ESCAPE_STR_QUOTE),
+            'class' => $sClass,
             'vals' => $sVals,
+            'bx_if:input' => array(
+                'condition' => !$bDisabled,
+                'content' => array(
+                    'attrs' => bx_convert_array2attrs($aAttrs),
+                )
+            ),
+            'bx_if:init' => array(
+                'condition' => !$bDisabled,
+                'content' => array(
+                    'id' => $sId,
+                    'name' => $aInput['name'],
+                    'url_get_recipients' => $aInput['ajax_get_suggestions'],
+                    'b_img' => isset($aInput['custom']['b_img']) ? (int)$aInput['custom']['b_img'] : 1,
+                    'only_once' => isset($aInput['custom']['only_once']) ? 1 : 0,
+                    'on_select' => isset($aInput['custom']['on_select']) ? $aInput['custom']['on_select']: 'null',
+                    'placeholder' => bx_html_attribute(isset($aInput['placeholder']) ? $aInput['placeholder'] : _t('_sys_form_paceholder_profiles_suggestions'), BX_ESCAPE_STR_QUOTE),
+                )
+            )
         ));
     }
 

+ 113 - 5
template/scripts/BxBasePrivacy.php

@@ -15,6 +15,9 @@ class BxBasePrivacy extends BxDolPrivacy
 {
     protected $_oTemplate;
 
+    protected $_sJsObjClass;
+    protected $_sJsObjName;
+
     public function __construct ($aOptions, $oTemplate)
     {
         parent::__construct ($aOptions);
@@ -23,14 +26,119 @@ class BxBasePrivacy extends BxDolPrivacy
             $this->_oTemplate = $oTemplate;
         else
             $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_sJsObjClass = 'BxDolPrivacy';
+        $this->_sJsObjName = 'oBxDolPrivacy' . bx_gen_method_name($this->_sObject);
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $this->_sObject);
+        $this->_aHtmlIds = array(
+            'group_element' => 'bx-form-element-' . $this->convertActionToField($this->_aObject['action']),
+            'group_custom_element' => 'sys-pgc-' . $sHtmlId,
+            'users_select_popup' => 'sys-privacy-usp-' . $sHtmlId . '-'
+        );
+    }
+
+    public function getJsObjectName()
+    {
+        return $this->_sJsObjName;
+    }
+
+    /**
+     * Get initialization section of comments box
+     *
+     * @return string
+     */
+    public function getJsScript($bDynamicMode = false)
+    {
+        $aParams = array(
+            'sObject' => $this->_sObject,
+            'sObjName' => $this->_sJsObjName,
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'aGroupSettings' => $this->_aGroupsSettings,
+            'aHtmlIds' => $this->_aHtmlIds
+        );
+
+        return $this->addCssJs($bDynamicMode) . $this->_oTemplate->_wrapInTagJsCode("if(window['" . $this->_sJsObjName . "'] == undefined) var " . $this->_sJsObjName . " = new " . $this->_sJsObjClass . "(" . json_encode($aParams) . ");");
+    }
+
+    protected function addCssJs($bDynamicMode = false)
+    {
+        $sInclude = '';
+
+        $sInclude .= $this->_oTemplate->addJs(array(
+            'jquery-ui/jquery-ui.custom.min.js',
+            'jquery-ui/jquery.ui.widget.min.js', 
+            'jquery-ui/jquery.ui.autocomplete.min.js', 
+            'BxDolPrivacy.js'
+        ), $bDynamicMode);
+
+        $sInclude .= $this->_oTemplate->addCss(array(
+            'forms.css', 
+            'privacy.css'
+        ), $bDynamicMode);
+
+        return $bDynamicMode ? $sInclude : '';
     }
 
-    protected function _addJsCss()
+    protected function getSelectGroup($aValues = array(), $aParams = array())
     {
-        /*
-        $this->_oTemplate->addJs('BxDolGrid.js');
-        $this->_oTemplate->addCss('grid.css');
-        */
+        $sJsObject = $this->getJsObjectName();
+
+        $oForm = BxDolForm::getObjectInstance($this->_sFormUsers, $this->_sFormDisplayUsersSelect);
+        $oForm->initChecker($aValues);
+
+        if($oForm->isSubmittedAndValid()) {
+            $aMembers = array();
+            if(($aMembersSearch = $oForm->getCleanValue('search')) !== false)
+                $aMembers = array_merge($aMembers, $aMembersSearch);
+
+            if(($aMembersList = $oForm->getCleanValue('list')) !== false)
+                $aMembers = array_merge($aMembers, $aMembersList);
+            
+            if(empty($aMembers) || !is_array($aMembers))
+                return array();
+
+            $iGroupCustomId = $oForm->getGroupCustomId();
+            if(!$iGroupCustomId) {
+                $this->deleteGroupCustom(array(
+                    'profile_id' => $oForm->getCleanValue('profile_id'),
+                    'content_id' => $oForm->getCleanValue('content_id'),
+                    'object' => $oForm->getCleanValue('object')
+                ));
+
+                $iGroupCustomId = $oForm->insert();
+                foreach($aMembers as $iMemberId)
+                    $this->_oDb->insertGroupCustomMember(array('group_id' => $iGroupCustomId, 'member_id' => $iMemberId));
+            }
+            else {
+                $this->_oDb->deleteGroupCustomMember(array('group_id' => $iGroupCustomId));
+
+                foreach($aMembers as $iMemberId)
+                    $this->_oDb->insertGroupCustomMember(array('group_id' => $iGroupCustomId, 'member_id' => $iMemberId));
+            }
+
+            return array('eval' => $sJsObject . '.onSelectUsers(oData);', 'content' => $oForm->getElementGroupCustom(array(
+                'value' => $iGroupCustomId, 
+                'value_members' => $aMembers
+            )));
+        }
+
+        $iGroupId = $oForm->getGroupId();
+        $sContent = BxTemplFunctions::getInstance()->transBox($this->_aHtmlIds['users_select_popup'] . $iGroupId, $this->_oTemplate->parseHtmlByName('privacy_users_select_popup.html', array(
+            'js_object' => $sJsObject,
+            'group' => $iGroupId,
+            'form' => $oForm->getCode(),
+            'form_id' => $oForm->getId()
+        )));
+
+        $aResult = array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false)));
+        if(isset($aParams['popup_only']) && (bool)$aParams['popup_only'] === true)
+            return $aResult;
+
+        return array_merge($aResult, array(
+            'eval' => $sJsObject . '.onSelectGroup(oData);', 
+            'content' => $oForm->getElementGroupCustom()
+        ));
     }
 }
 

+ 196 - 0
template/scripts/BxBasePrivacyFormGroupCustom.php

@@ -0,0 +1,196 @@
+<?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
+ * @{
+ */
+
+class BxBasePrivacyFormGroupCustom extends BxTemplFormView
+{
+    protected $_iProfileId;
+    protected $_iContentId;
+
+    protected $_sObject;
+    protected $_oObject;
+
+    protected $_iGroupId;
+    protected $_aGroupSettings;
+    
+    protected $_iGroupCustomId;
+    protected $_aGroupCustomInfo;
+
+    public function __construct($aInfo, $oTemplate)
+    {
+        parent::__construct($aInfo, $oTemplate);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+        $this->_iContentId = 0;
+
+        $this->_sObject = '';
+        $this->_oObject = null;
+
+        $this->_iGroupId = 0;
+        $this->_aGroupSettings = array();
+
+        $this->_iGroupCustomId = 0;
+        $this->_aGroupCustomInfo = array();
+
+        switch($this->aParams['display']) {
+            case 'sys_privacy_group_custom_manage':
+                $this->aInputs['action']['value'] = 'select_group';
+                break;
+        }
+    }
+
+    public function initChecker($aValues = array(), $aSpecificValues = array())
+    {
+        $this->initData($aValues);
+
+        return parent::initChecker($aValues, $aSpecificValues);
+    }
+
+    public function getGroupId()
+    {
+        return $this->_iGroupId;
+    }
+
+    public function getGroupCustomId()
+    {
+        return $this->_iGroupCustomId;
+    }
+
+    public function getElementGroupCustom($aParams = array())
+    {
+        $this->initData($aParams);
+
+        $sName = 'sys-pgc-' . str_replace('_', '-', $this->_sObject);
+        $iValue = !empty($aParams['value']) ? (int)$aParams['value'] : $this->_iGroupCustomId;
+        $aValueMembers = array();
+        if(!empty($aParams['value_members']) && is_array($aParams['value_members']))
+            $aValueMembers = $aParams['value_members'];
+        else if($this->_iGroupCustomId)
+            $aValueMembers = $this->_aGroupCustomInfo['members'];
+
+        $aInput = array(
+            'type' => 'hidden',
+            'name' => $sName . '-id',
+            'caption' => '',
+            'value' => $iValue,
+            'db' => array(
+                'pass' => 'Int'
+            )
+        );
+
+        $aInputMembers = array(
+            'type' => 'custom',
+            'name' => $sName . '-members',
+            'caption' => '',
+            'value' => $aValueMembers,
+            'ajax_get_suggestions' => BX_DOL_URL_ROOT . bx_append_url_params('privacy.php', array(
+                'object' => $this->_sObject,
+                'action' => 'users_list',
+                'group' => $this->_iGroupId
+            )),
+            'attrs' => array(
+                'disabled' => 'disabled'
+            )
+        );
+
+        return $this->oTemplate->parseHtmlByName('privacy_group_custom.html', array(
+            'js_object' => $this->_oObject->getJsObjectName(),
+            'html_id' => $sName,
+            'input_id' => $this->genInputStandard($aInput),
+            'input_members' => $this->genCustomInputUsernamesSuggestions($aInputMembers)
+        ));
+    }
+
+    protected function genCustomInputSearch($aInput)
+    {
+        $aInput['ajax_get_suggestions'] = BX_DOL_URL_ROOT . bx_append_url_params('privacy.php', array(
+            'object' => $this->_sObject,
+            'action' => 'users_list',
+            'group' => $this->_iGroupId
+        ));
+
+        return $this->genCustomInputUsernamesSuggestions($aInput);
+    }
+    
+    protected function genCustomInputList($aInput)
+    {
+        $iProfileId = bx_get_logged_profile_id();
+
+        $oConnection = BxDolConnection::getObjectInstance($this->_aGroupSettings['connection']);
+        if(!$oConnection)
+            return '';
+
+        $aConnectedIds = $oConnection->getConnectedContent($iProfileId, true);
+
+        $aSelected = array();
+        if(!empty($this->_aGroupCustomInfo) && is_array($this->_aGroupCustomInfo))
+            $aSelected = $this->_aGroupCustomInfo['members'];
+
+        $aTmplVarsUsers = array();
+        if(!empty($aConnectedIds) && is_array($aConnectedIds)) {
+            $aCheckbox = $aInput;
+            $aCheckbox['type'] = 'checkbox';
+            $aCheckbox['name'] .= '[]';
+
+            foreach($aConnectedIds as $iConnectedId) {
+                $oProfile = BxDolProfile::getInstanceMagic($iConnectedId);
+                if(!$oProfile)
+                    continue;
+
+                $aCheckbox['value'] = $iConnectedId;
+                $aCheckbox['checked'] = in_array($iConnectedId, $aSelected) ? 1 : 0;
+
+                $aTmplVarsUsers[] = array(
+                    'checkbox' => $this->genInput($aCheckbox),
+                    'unit' => $oProfile->getUnit()
+                );
+            }
+        }      
+
+        if(empty($aTmplVarsUsers))
+            $aTmplVarsUsers = MsgBox(_t('_Empty'));
+
+        return $this->oTemplate->parseHtmlByName('privacy_users_select_list.html', array(
+            'bx_repeat:users' => $aTmplVarsUsers
+        ));
+    }
+
+    protected function initData($aData)
+    {
+        if(!empty($aData['profile_id']))
+            $this->_iProfileId = (int)$aData['profile_id'];
+
+        if(!empty($aData['content_id']))
+            $this->_iContentId = (int)$aData['content_id'];
+
+        if(!empty($aData['object'])) {
+            $this->_sObject = $aData['object'];
+            $this->_oObject = BxDolPrivacy::getObjectInstance($this->_sObject);
+        }
+
+        if(!empty($aData['group_id'])) {
+            $this->_iGroupId = (int)$aData['group_id'];
+            $this->_aGroupSettings = $this->_oObject ? $this->_oObject->getGroupSettings($this->_iGroupId) : array();
+        }
+
+        if(!empty($this->_iProfileId) && !empty($this->_sObject) && !empty($this->_iGroupId)) {
+            $this->_aGroupCustomInfo = $this->_oObject->getGroupCustom(array(
+                'type' => 'pcog_ext', 
+                'profile_id' => $this->_iProfileId,
+                'content_id' => $this->_iContentId,
+                'object' => $this->_sObject,
+                'group_id' => $this->_iGroupId
+            ));
+
+            if(!empty($this->_aGroupCustomInfo) && is_array($this->_aGroupCustomInfo))
+                $this->_iGroupCustomId = (int)$this->_aGroupCustomInfo['id'];
+        }
+    }
+}
+
+/** @} */

Деякі файли не було показано, через те що забагато файлів було змінено