Prechádzať zdrojové kódy

Upgrade script from 11.0.1 to 11.0.2

Alex Trofimov 6 rokov pred
rodič
commit
3e55dd8989
20 zmenil súbory, kde vykonal 10579 pridanie a 0 odobranie
  1. 9 0
      upgrade/files/11.0.1-11.0.2/check.php
  2. 448 0
      upgrade/files/11.0.1-11.0.2/files/inc/classes/BxDolAclQuery.php
  3. 2890 0
      upgrade/files/11.0.1-11.0.2/files/inc/classes/BxDolTemplate.php
  4. 249 0
      upgrade/files/11.0.1-11.0.2/files/inc/js/classes/BxDolVoteReactions.js
  5. 1131 0
      upgrade/files/11.0.1-11.0.2/files/inc/js/functions.js
  6. 12 0
      upgrade/files/11.0.1-11.0.2/files/inc/version.inc.php
  7. 29 0
      upgrade/files/11.0.1-11.0.2/files/modules/base/groups/classes/BxBaseModGroupsConfig.php
  8. 823 0
      upgrade/files/11.0.1-11.0.2/files/modules/base/groups/classes/BxBaseModGroupsModule.php
  9. 36 0
      upgrade/files/11.0.1-11.0.2/files/modules/base/profile/classes/BxBaseModProfileConfig.php
  10. 1847 0
      upgrade/files/11.0.1-11.0.2/files/modules/base/profile/classes/BxBaseModProfileModule.php
  11. 68 0
      upgrade/files/11.0.1-11.0.2/files/modules/base/text/classes/BxBaseModTextMenuAttachments.php
  12. 641 0
      upgrade/files/11.0.1-11.0.2/files/template/css/forms.css
  13. 200 0
      upgrade/files/11.0.1-11.0.2/files/template/css/media-phone.css
  14. 383 0
      upgrade/files/11.0.1-11.0.2/files/template/css/vote.css
  15. 414 0
      upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseConfig.php
  16. 655 0
      upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseFunctions.php
  17. 308 0
      upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseVote.php
  18. 429 0
      upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseVoteReactions.php
  19. 3 0
      upgrade/files/11.0.1-11.0.2/script.php
  20. 4 0
      upgrade/files/11.0.1-11.0.2/sql.sql

+ 9 - 0
upgrade/files/11.0.1-11.0.2/check.php

@@ -0,0 +1,9 @@
+<?php
+
+$mixCheckResult = 'Update can not be applied';
+
+$sVer = $this->oDb->getOne("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+if ('11.0.1' == $sVer)
+    $mixCheckResult = true;
+
+return $mixCheckResult;

+ 448 - 0
upgrade/files/11.0.1-11.0.2/files/inc/classes/BxDolAclQuery.php

@@ -0,0 +1,448 @@
+<?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
+ * @{
+ */
+
+/**
+ * Database queries for ACL
+ * @see BxDolAcl
+ */
+class BxDolAclQuery extends BxDolDb implements iBxDolSingleton
+{
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolAclQuery();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    function getLevels($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tal`.`Order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause .= "AND `tal`.`ID`=:id";
+                $sLimitClause .= "LIMIT 1";
+                break;
+
+            case 'all_active':
+                $sWhereClause .= "AND `tal`.`Active`='yes'";
+                break;
+
+            case 'all_active_purchasble_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                $sWhereClause .= "AND `tal`.`Active`='yes' AND `tal`.`Purchasable`='yes'";
+                break;
+
+            case 'all_active_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                $sWhereClause .= "AND `tal`.`Active`='yes'";
+                break;
+
+            case 'all_active_not_automatic_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                $sWhereClause .= "AND `tal`.`Active`='yes' AND `tal`.`ID` NOT IN (" . $this->implode_escape(array(
+                    MEMBERSHIP_ID_NON_MEMBER,
+                    MEMBERSHIP_ID_ACCOUNT,
+                    MEMBERSHIP_ID_UNCONFIRMED,
+                    MEMBERSHIP_ID_PENDING,
+                    MEMBERSHIP_ID_SUSPENDED
+                    
+                )) . ")";
+                break;
+
+            case 'all_pair':
+                $aMethod['name'] = "getPairs";
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'name';
+                break;
+
+            case 'all_order_id':
+                $sOrderClause = "ORDER BY `tal`.`ID` ASC";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tal`.`ID` AS `id`,
+                `tal`.`Name` AS `name`,
+                `tal`.`Icon` AS `icon`,
+                `tal`.`Description` AS `description`,
+                `tal`.`Active` AS `active`,
+                `tal`.`Purchasable` AS `purchasable`,
+                `tal`.`Removable` AS `removable`,
+                `tal`.`QuotaSize` AS `quota_size`,
+                `tal`.`QuotaNumber` AS `quota_number`,
+                `tal`.`QuotaMaxFileSize` AS `quota_max_file_size`,
+                `tal`.`Order` AS `order`" . $sSelectClause . "
+            FROM `sys_acl_levels` AS `tal` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getActions($aParams, &$aItems, $bReturnCount = true)
+    {
+        $sMemoryKey = '';
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `taa`.`Title` ASC";
+
+           
+        switch($aParams['type']) {
+            case 'by_names_and_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['module']
+                );
+
+                $sWhereClause .= " AND `taa`.`Name` IN(" . $this->implode_escape($aParams['value']) . ") AND `taa`.`Module` = :module ";
+                $sMemoryKey = 'BxDolAclQuery::getActions' . $aParams['type'] . $aParams['module'] . $sWhereClause;
+                break;
+
+            case 'by_names':
+                $sWhereClause .= " AND `taa`.`Name` IN(" . $this->implode_escape($aParams['value']) . ")";
+                break;
+
+            case 'by_level_id':
+            	$aMethod['params'][1] = array(
+                	'level_id' => $aParams['value'],
+                	'level_code' => pow(2, ($aParams['value'] - 1))
+                );
+
+                $sSelectClause .= ", `tam`.`AllowedCount` AS `allowed_count`, `tam`.`AllowedPeriodLen` AS `allowed_period_len`, `tam`.`AllowedPeriodStart` AS `allowed_period_start`, `tam`.`AllowedPeriodEnd` AS `allowed_period_end`, `tam`.`AdditionalParamValue` AS `additional_param_value` ";
+                $sJoinClause .= "LEFT JOIN `sys_acl_matrix` AS `tam` ON `taa`.`ID`=`tam`.`IDAction` ";
+                $sWhereClause .= "AND `tam`.`IDLevel`=:level_id AND (`taa`.`DisabledForLevels`='0' OR `taa`.`DisabledForLevels`&:level_code=0)";
+                break;
+
+            case 'by_level_id_key_id':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = array(
+                	'level_id' => $aParams['value']
+                );
+
+                $sSelectClause .= ", `tam`.`AllowedCount` AS `allowed_count`, `tam`.`AllowedPeriodLen` AS `allowed_period_len` ";
+                $sJoinClause .= "LEFT JOIN `sys_acl_matrix` AS `tam` ON `taa`.`ID`=`tam`.`IDAction` ";
+                $sWhereClause .= "AND `tam`.`IDLevel`=:level_id";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `taa`.`Module`";
+                break;
+
+            case 'counter_by_levels':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'level_id';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", `tam`.`IDLevel` AS `level_id`, COUNT(`tam`.`IDAction`) AS `counter`";
+                $sJoinClause = "LEFT JOIN `sys_acl_matrix` AS `tam` ON `taa`.`ID`=`tam`.`IDAction` AND (`taa`.`DisabledForLevels`='0' OR `taa`.`DisabledForLevels`&POW(2, `tam`.`IDLevel`-1)=0) ";
+                $sGroupClause = "GROUP BY `tam`.`IDLevel`";
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `taa`.`ID` AS `id`,
+                `taa`.`Module` AS `module`,
+                `taa`.`Name` AS `name`,
+                `taa`.`Title` AS `title`,
+                `taa`.`Countable` AS `countable`,
+                `taa`.`DisabledForLevels` AS `disabled_for_levels`" . $sSelectClause . "
+            FROM `sys_acl_actions` AS `taa` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+
+        if ($sMemoryKey) {
+            array_unshift($aMethod['params'], $sMemoryKey, $aMethod['name']);
+            $aItems = call_user_func_array(array($this, 'fromMemory'), $aMethod['params']);
+            return $bReturnCount ? count($aItems) : !empty($aItems);
+        }
+
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    /**
+     * Fetch the last purchased/assigned membership that is still active for the given profile.
+     *
+     * NOTE. Don't use cache here, because it's causing an error, if a number of memberrship levels are purchased at the same time.
+     * fromMemory returns the same DateExpires because setMembership (old buyMembership) function is called in cycle in the same session.
+     */
+    function getLevelCurrent($iProfileId, $iTime = 0)
+    {
+        $iTime = $iTime == 0 ? time() : (int)$iTime;
+
+        $sSql = $this->prepare("
+            SELECT  `sys_acl_levels_members`.`IDLevel` as `id`,
+                    `sys_acl_levels`.`Name` AS `name`,
+                    `sys_acl_levels`.`QuotaSize` AS `quota_size`,
+                    `sys_acl_levels`.`QuotaNumber` AS `quota_number`,
+                    `sys_acl_levels`.`QuotaMaxFileSize` AS `quota_max_file_size`,
+                    UNIX_TIMESTAMP(`sys_acl_levels_members`.`DateStarts`) as `date_starts`,
+                    UNIX_TIMESTAMP(`sys_acl_levels_members`.`DateExpires`) as `date_expires`,
+                    `sys_acl_levels_members`.`TransactionID` AS `transaction_id`,
+                    `sys_profiles`.`status`
+            FROM `sys_acl_levels_members`
+            RIGHT JOIN `sys_profiles` ON `sys_acl_levels_members`.IDMember = `sys_profiles`.`id`
+                AND (`sys_acl_levels_members`.DateStarts IS NULL OR `sys_acl_levels_members`.DateStarts <= FROM_UNIXTIME(?))
+                AND (`sys_acl_levels_members`.DateExpires IS NULL OR `sys_acl_levels_members`.DateExpires > FROM_UNIXTIME(?))
+            LEFT JOIN `sys_acl_levels` ON `sys_acl_levels_members`.IDLevel = `sys_acl_levels`.ID
+            WHERE `sys_profiles`.`id` = ?
+            ORDER BY `sys_acl_levels_members`.DateStarts DESC
+            LIMIT 1", $iTime, $iTime, $iProfileId);
+
+        return $this->fromMemory('BxDolAclQuery::getLevelCurrent' . $iProfileId . $iTime, 'getRow', $sSql);
+    }
+
+    function getLevelByIdCached($iLevel)
+    {
+        $sQuery = $this->prepare("SELECT
+                `tal`.`ID` AS `id`,
+                `tal`.`Name` AS `name`,
+                `tal`.`QuotaSize` AS `quota_size`,
+                `tal`.`QuotaNumber` AS `quota_number`,
+                `tal`.`QuotaMaxFileSize` AS `quota_max_file_size`
+            FROM `sys_acl_levels` AS `tal`
+            WHERE `tal`.`ID`=?
+            LIMIT 1", $iLevel);
+        return $this->fromCache('sys_acl_levels' . $iLevel, 'getRow', $sQuery);
+    }
+
+    function getAction($iMembershipId, $iActionId)
+    {
+        $sQuery = $this->prepare("SELECT
+                `tam`.`IDAction` AS `id`,
+                `taa`.`Name` AS `name`,
+                `taa`.`Title` AS `title`,
+                `tam`.`AllowedCount` AS `allowed_count`,
+                `tam`.`AllowedPeriodLen` AS `allowed_period_len`,
+                UNIX_TIMESTAMP(`tam`.`AllowedPeriodStart`) as `allowed_period_start`,
+                UNIX_TIMESTAMP(`tam`.`AllowedPeriodEnd`) as `allowed_period_end`,
+                `tam`.`AdditionalParamValue` AS `additional_param_value`
+            FROM `sys_acl_actions` AS `taa`
+            LEFT JOIN `sys_acl_matrix` AS `tam` ON `tam`.`IDAction` = `taa`.`ID` AND `tam`.`IDLevel` = ?
+            WHERE `taa`.`ID` = ?", $iMembershipId, $iActionId);
+        return $this->fromMemory('BxDolAclQuery::getAction' . $iMembershipId . $iActionId, 'getRow', $sQuery);
+    }
+
+    function getActionTrack($iActionId, $iProfileId)
+    {
+        $sQuery = $this->prepare("SELECT
+                `taat`.`ActionsLeft` AS `actions_left`,
+                UNIX_TIMESTAMP(`taat`.`ValidSince`) as `valid_since`
+            FROM `sys_acl_actions_track` AS `taat`
+            WHERE `taat`.`IDAction`=? AND `taat`.`IDMember`=?", $iActionId, $iProfileId);
+        return $this->getRow($sQuery);
+    }
+
+    function insertActionTarck($iActionId, $iProfileId, $iActionsLeft, $iValidSince)
+    {
+        $sQuery = $this->prepare("INSERT INTO `sys_acl_actions_track`(`IDAction`, `IDMember`, `ActionsLeft`, `ValidSince`) VALUES (?, ?, ?, FROM_UNIXTIME(?))", $iActionId, $iProfileId, $iActionsLeft, $iValidSince);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    function updateActionTrack($iActionId, $iProfileId, $iActionsLeft, $iValidSince = 0)
+    {
+    	$aBindings = array(
+    		'actions_left' => $iActionsLeft,
+    		'action_id' => $iActionId,
+    		'member_id' => $iProfileId
+    	);
+
+        $sUpdateAddon = "";
+        if($iValidSince != 0) {
+        	$aBindings['valid_since'] = $iValidSince;
+
+            $sUpdateAddon = ", ValidSince=FROM_UNIXTIME(:valid_since)";
+        }
+
+        $sQuery = "UPDATE `sys_acl_actions_track` SET `ActionsLeft`=:actions_left" . $sUpdateAddon . " WHERE `IDAction`=:action_id AND `IDMember`=:member_id";
+        return (int)$this->query($sQuery, $aBindings) > 0;
+    }
+
+    function insertLevelByProfileId($iProfileId, $iMembershipId, $iDateStarts, $aPeriod, $sTransactionId)
+    {
+    	$aBindings = array(
+            'member_id' => $iProfileId,
+            'level_id' => $iMembershipId,
+            'transaction_id' => $sTransactionId,
+            'date_starts' => $iDateStarts
+        );
+
+    	$sSetClause = '';
+    	if((int)$aPeriod['period'] != 0) {
+            $aBindings['period'] = (int)$aPeriod['period'];
+
+            switch($aPeriod['period_unit']) {
+                case MEMBERSHIP_PERIOD_UNIT_DAY:
+                case MEMBERSHIP_PERIOD_UNIT_WEEK:
+                    if($aPeriod['period_unit'] == MEMBERSHIP_PERIOD_UNIT_WEEK)
+                        $aBindings['period'] *= 7;
+
+                    $sSetClause = "DATE_ADD(FROM_UNIXTIME(:date_starts), INTERVAL :period DAY)";
+                    break;
+
+                case MEMBERSHIP_PERIOD_UNIT_MONTH:
+                    $sSetClause = "DATE_ADD(FROM_UNIXTIME(:date_starts), INTERVAL :period MONTH)";
+                    break;
+
+                case MEMBERSHIP_PERIOD_UNIT_YEAR:
+                    $sSetClause = "DATE_ADD(FROM_UNIXTIME(:date_starts), INTERVAL :period YEAR)";
+                    break;
+            }
+
+            if(!empty($sSetClause) && !empty($aPeriod['period_reserve'])) {
+                $aBindings['reserve'] = (int)$aPeriod['period_reserve'];
+
+                $sSetClause = "DATE_ADD(" . $sSetClause . ", INTERVAL :reserve DAY)";
+            }
+
+            if(!empty($sSetClause))
+                $sSetClause = ", `DateExpires`=" . $sSetClause;
+    	}
+
+        $sQuery = $this->prepare("INSERT `sys_acl_levels_members` SET `IDMember`=:member_id, `IDLevel`=:level_id, `DateStarts`=FROM_UNIXTIME(:date_starts), `TransactionID`=:transaction_id" . $sSetClause);
+        return (int)$this->query($sQuery, $aBindings) > 0;
+    }
+
+    function deleteLevelByProfileId($iProfileId, $bAll = false)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_acl_levels_members` WHERE `IDMember` = ? " . ($bAll ? "" : " AND (`DateExpires` IS NULL OR `DateExpires` > NOW())"), $iProfileId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+	function deleteLevelBy($aWhere)
+    {
+    	if(empty($aWhere))
+    		return false;
+
+        $sQuery = "DELETE FROM `sys_acl_levels_members` WHERE " . $this->arrayToSQL($aWhere, ' AND ');
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    function maintenance($iDaysToCleanMemLevels = 0)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_acl_levels_members` WHERE `DateExpires` < NOW() - INTERVAL ? DAY", $iDaysToCleanMemLevels);
+        if ($iDeleteMemLevels = $this->query($sQuery))
+            $this->query("OPTIMIZE TABLE `sys_acl_levels_members`");
+        return $iDeleteMemLevels;
+    }
+
+    function clearActionsTracksForMember($iMemberId)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_acl_actions_track` WHERE `IDMember` = ?", (int)$iMemberId);
+	    return $this->query($sQuery);
+    }
+
+	function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
+    {
+    	$sJoin = $sWhere = ""; 
+        $iLevelId = !is_array($mixedLevelId) ? $mixedLevelId : 0;
+        if (!$iLevelId && is_array($mixedLevelId) && 1 == count($mixedLevelId)) {
+            $a = array_values($mixedLevelId);
+            $iLevelId = array_shift($a);
+        }
+
+        // unconfirmed
+        if (MEMBERSHIP_ID_UNCONFIRMED == $iLevelId) {
+            return array(
+                'where' => " AND `cblasp_a`.`email_confirmed` = 0 ",
+                'join' => " INNER JOIN `sys_profiles` AS `cblasp_p` ON (`" . $sContentTable . "`.`" . $sContentField . "`=`cblasp_p`.`id`) INNER JOIN `sys_accounts` AS `cblasp_a` ON (`cblasp_a`.`id`=`cblasp_p`.`account_id`) ",
+            );
+        }
+        // standard
+        elseif (MEMBERSHIP_ID_STANDARD == $iLevelId) {
+
+            $sWhere .= " AND (`tlm`.`DateStarts` IS NULL OR `tlm`.`DateStarts` <= NOW()) AND (`tlm`.`DateExpires` IS NULL OR `tlm`.`DateExpires` > NOW()) AND `tlm`.`IDMember` IS NULL AND `cblasp_a`.`email_confirmed` != 0 ";
+
+            $sJoin .= " LEFT JOIN `sys_acl_levels_members` AS `tlm` ON `" . $sContentTable . "`.`" . $sContentField . "`=`tlm`.`IDMember` INNER JOIN `sys_profiles` AS `cblasp_p` ON (`" . $sContentTable . "`.`" . $sContentField . "`=`cblasp_p`.`id`) INNER JOIN `sys_accounts` AS `cblasp_a` ON (`cblasp_a`.`id`=`cblasp_p`.`account_id`) ";
+
+            return array(
+                'where' => $sWhere,
+                'join' => $sJoin
+            );
+        }
+        // other levels
+        else {
+
+            if(is_array($mixedLevelId))
+                $sWhere .= " AND `tlm`.`IDLevel` IN (" . $this->implode_escape($mixedLevelId) . ")";
+            else
+                $sWhere .= $this->prepareAsString(" AND `tlm`.`IDLevel` = ?", (int)$mixedLevelId);
+
+            $sWhere .= " AND (`tlm`.`DateStarts` IS NULL OR `tlm`.`DateStarts` <= NOW()) AND (`tlm`.`DateExpires` IS NULL OR `tlm`.`DateExpires` > NOW()) AND `cblasp_a`.`email_confirmed` != 0 ";
+
+            $sJoin .= " INNER JOIN `sys_acl_levels_members` AS `tlm` ON `" . $sContentTable . "`.`" . $sContentField . "`=`tlm`.`IDMember` INNER JOIN `sys_profiles` AS `cblasp_p` ON (`" . $sContentTable . "`.`" . $sContentField . "`=`cblasp_p`.`id`) INNER JOIN `sys_accounts` AS `cblasp_a` ON (`cblasp_a`.`id`=`cblasp_p`.`account_id`) ";
+
+            return array(
+                'where' => $sWhere,
+                'join' => $sJoin
+            );
+        }
+    }
+
+    function getProfilesByMembership($mixedLevelId)
+    {
+    	$aSqlParts = $this->getContentByLevelAsSQLPart('sys_profiles', 'id', $mixedLevelId);
+
+    	$sSql = $this->prepare("SELECT `sys_profiles`.* FROM `sys_profiles`" . $aSqlParts['join'] . " WHERE 1" . $aSqlParts['where']);
+    	return $this->getAll($sSql); 
+    }
+}
+
+/** @} */

+ 2890 - 0
upgrade/files/11.0.1-11.0.2/files/inc/classes/BxDolTemplate.php

@@ -0,0 +1,2890 @@
+<?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
+ * @{
+ */
+
+ini_set('pcre.backtrack_limit', 1000000);
+
+define('BX_DOL_TEMPLATE_CODE_KEY', 'skin');
+define('BX_DOL_TEMPLATE_MIX_KEY', 'mix');
+define('BX_DOL_TEMPLATE_DEFAULT_CODE', 'protean');
+define('BX_DOL_TEMPLATE_FOLDER_ROOT', 'template');
+
+define('BX_DOL_TEMPLATE_INJECTIONS_CACHE', 'sys_injections.inc');
+
+define('BX_DOL_TEMPLATE_CHECK_IN_BOTH', 'both');
+define('BX_DOL_TEMPLATE_CHECK_IN_BASE', 'base');
+define('BX_DOL_TEMPLATE_CHECK_IN_TMPL', 'tmpl');
+
+define('BX_DOL_COLOR_BG', 'bg');
+define('BX_DOL_COLOR_FT', 'ft');
+
+define('BX_DOL_PAGE_WIDTH', '1024px');
+
+/**
+ * Page display levels.
+ * Note. Both levels may refer to the same HTML templates. 
+ * 
+ * 'System' level. It uses page's NameIndex and
+ * is mainly used for pages which aren't registered 
+ * in 'sys_objects_page' table. Also it's used 
+ * in 'Injections' engine.  
+ */
+define('BX_PAGE_DEFAULT', 0); ///< default, regular page
+define('BX_PAGE_CLEAR', 2); ///< clear page, without any headers and footers
+define('BX_PAGE_EMBED', 22); ///< page used for embeds
+define('BX_PAGE_POPUP', 44); ///< popup page, without any headers and footers
+define('BX_PAGE_TRANSITION', 150); ///< transition page with redirect to display some msg, like 'please wait', without headers footers
+
+/**
+ * 'Builder based' level. It uses page's Type 
+ * from BxDolPage class and is used for pages 
+ * which are registered in 'sys_objects_page' table.
+ * Changeable in Studio -> Pages Builder -> Settings. 
+ */
+define('BX_PAGE_TYPE_DEFAULT', 1); ///< default, regular page
+define('BX_PAGE_TYPE_DEFAULT_WO_HF', 2); ///< clear page, without any headers and footers
+
+/**
+ * Template engine.
+ *
+ * An object of the class allows to:
+ *  1. Manage HTML templates.
+ *  2. Get URL/path for any template image/icon.
+ *  3. Attach CSS/JavaScript files to the output.
+ *  4. Add some content to any template key using Injection engine.
+ *
+ *
+ * Avalable constructions.
+ *  1. &lt;bx_include_auto:template_name.html /&gt; - the content of the file would be inserted. File would be taken from current template if it existes there, and from base directory otherwise.
+ *  2. &lt;bx_include_base:template_name.html /&gt; - the content of the file would be inserted. File would be taken from base directory.
+ *  3. &lt;bx_include_tmpl:template_name.html /&gt; - the content of the file would be inserted. File would be taken from tmpl_xxx directory.
+ *  4. &lt;bx_url_root /&gt; - the value of BX_DOL_URL_ROOT variable will be inserted.
+ *  5. &lt;bx_url_admin /&gt; - the value of BX_DOL_URL_ADMIN variable will be inserted.
+ *  6. &lt;bx_text:_language_key /&gt; - _language_key will be translated using language file(function _t()) and inserted.
+       &lt;bx_text_js:_language_key /&gt; - _language_key will be translated using language file(function _t()) and inserted, use it to insert text into js string.
+       &lt;bx_text_attribute:_language_key /&gt; - _language_key will be translated using language file(function _t()) and inserted, use it to insert text into html attribute.
+ *  7. &lt;bx_image_url:image_file_name /&gt; - image with 'image_file_name' file name will be searched in the images folder of current template.
+ *     If it's not found, then it will be searched in the images folder of base template. On success full URL will be inserted, otherwise an empty string.
+ *  8. &lt;bx_icon_url:icon_file_name /&gt; - the same with &lt;bx_image_url:image_file_name /&gt;, but icons will be searched in the images/icons/ folders.
+ *  9. &lt;bx_injection:injection_name /&gt; - will be replaced with injections registered with the page and injection_name in the `sys_injections`/`sys_injections_admin`/ tables.
+ *  10. &lt;bx_if:tag_name&gt;some_HTML&lt;/bx_if:tag_name&gt; - will be replaced with provided content if the condition is true, and with empty string otherwise.
+ *  11. &lt;bx_repeat:cycle_name&gt;some_HTML&lt;/bx_repeat:cycle_name&gt; - an inner HTML content will be repeated in accordance with received data.
+ *
+ *
+ * Related classes:
+ *  BxDolTemplateAdmin - for processing admin templates.
+ *  Template classes in modules - for processing modiles' templates.
+ *
+ *
+ * Global variables:
+ *  oSysTemplate - is used for template processing in user part.
+ *  oAdmTemplate - is used for template processing in admin part.
+ *
+ *
+ * Add injection:
+ *  1. Register it in the `sys_injections` table or `sys_injections_admin` table for admin panel.
+ *  2. Clear injections cache(sys_injections.inc and sys_injections_admin.inc in cache folder).
+ *
+ *
+ * Predefined template keys to add injections:
+ *  1. injection_head - add injections in the &lt;head&gt; tag.
+ *  2. injection_body - add ingection(attribute) in the &lt;body&gt; tag.
+ *  3. injection_header - add injection inside the &lt;body&gt; tag at the very beginning.
+ *  4. injection_logo_before - add injection at the left of the main logo(inside logo's DIV).
+ *  5. injection_logo_after - add injection at the right of the main logo(inside logo's DIV).
+ *  6. injection_between_logo_top_menu - add injection between logo and top menu.
+ *  7. injection_top_menu_before - add injection at the left of the top menu(inside top menu's DIV).
+ *  8. injection_top_menu_after - add injection at the right of the top menu(inside top menu's DIV).
+ *  13. injection_content_before - add injection just before main content(inside content's DIV).
+ *  14. injection_content_after - add injection just after main content(inside content's DIV).
+ *  15. injection_between_content_footer - add injection between content and footer.
+ *  16. injection_footer_before - add injection at the left of the footer(inside footer's DIV).
+ *  17. injection_footer_after - add injection at the right of the footer(inside footer's DIV).
+ *  18. injection_footer - add injection inside the &lt;body&gt; tag at the very end.
+ *
+ *
+ * Example of usage:
+ * @code
+ *  $oSysTemplate = BxDolTemplate::getInstance();
+ *
+ *  $oSysTemplate->addCss(array('test1.css', 'test2.css'));
+ *  $oSysTemplate->addJs(array('test1.js', 'test2.js'));
+ *  $oSysTemplate->parseHtmlByName('messageBox.html', array(
+ *    'id' => $iId,
+ *     'msgText' => $sText,
+ *     'bx_if:timer' => array(
+ *        'condition' => $iTimer > 0,
+ *        'content' => array(
+ *           'id' => $iId,
+ *           'time' => 1000 * $iTimer,
+ *           'on_close' => $sOnClose,
+ *        )
+ *     ),
+ *     'bx_if:timer' => array(
+ *        array(
+ *           'name' => $sName,
+ *           'title' => $sTitle
+ *        ),
+ *        array(
+ *           'name' => $sName,
+ *           'title' => $sTitle
+ *        )
+ *     )
+ *  ));
+ * @endcode
+ *
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+class BxDolTemplate extends BxDolFactory implements iBxDolSingleton
+{
+    protected static $_aColors = array(
+    	'red1' => array(216, 9, 96), 
+    	'red1-dark' => array(194, 7, 86), 
+    	'red2' => array(231, 68, 30), 
+    	'red2-dark' => array(207, 60, 25), 
+    	'red3' => array(243, 143, 0), 
+    	'red3-dark' => array(218, 128, 0), 
+    	'green1' => array(96, 174, 0), 
+    	'green1-dark' => array(86, 156, 0), 
+    	'green2' => array(209, 211, 0), 
+    	'green2-dark' => array(186, 188, 0), 
+    	'green3' => array(48, 116, 36), 
+    	'green3-dark' => array(43, 104, 32), 
+    	'blue1' => array(10, 61, 143), 
+    	'blue1-dark' => array(9, 54, 128), 
+    	'blue2' => array(0, 164, 165), 
+    	'blue2-dark' => array(0, 146, 148), 
+    	'blue3' => array(0, 160, 206), 
+    	'blue3-dark' => array(0, 143, 184), 
+    	'gray' => array(97, 97, 97), 
+    	'gray-dark' => array(87, 87, 87)
+    );
+
+    /**
+     * Main fields
+     */
+    protected $_sName;
+    protected $_sPrefix;
+    protected $_sRootPath;
+    protected $_sRootUrl;
+    protected $_sSubPath;
+    protected $_sInjectionsTable;
+    protected $_sInjectionsCache;
+    protected $_sCode;
+    protected $_sCodeKey;
+    protected $_iMix;
+    protected $_sMixKey;
+    protected $_sKeyWrapperHtml;
+    protected $_sFolderHtml;
+    protected $_sFolderCss;
+    protected $_sFolderImages;
+    protected $_sFolderIcons;
+    protected $_aTemplates;
+
+    protected $_aLocations;
+    protected $_aLocationsJs;
+
+    /**
+     * Cache related fields
+     */
+    protected $_bCacheEnable;
+    protected $_sCacheFolderUrl;
+    protected $_sCachePublicFolderUrl;
+    protected $_sCachePublicFolderPath;
+    protected $_sCacheFilePrefix;
+
+    protected $_bImagesInline;
+    protected $_iImagesMaxSize;
+
+    protected $_bCssLess;
+    protected $_bCssCache;
+    protected $_bCssMinify;
+    protected $_bCssArchive;
+    protected $_sCssLessPrefix;
+    protected $_sCssCachePrefix;
+
+    protected $_bJsLess;
+    protected $_bJsCache;
+    protected $_bJsMinify;
+    protected $_bJsArchive;
+    protected $_sJsCachePrefix;
+
+    protected $aPage;
+    protected $aPageContent;
+
+    protected $_oTemplateConfig;
+    protected $_oTemplateFunctions;
+
+    /**
+     * Constructor
+     */
+    protected function __construct($sRootPath = BX_DIRECTORY_PATH_ROOT, $sRootUrl = BX_DOL_URL_ROOT)
+    {
+        if(isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->_sPrefix = 'BxDolTemplate';
+
+        $this->_sRootPath = $sRootPath;
+        $this->_sRootUrl = $sRootUrl;
+        $this->_sInjectionsTable = 'sys_injections';
+        $this->_sInjectionsCache = BX_DOL_TEMPLATE_INJECTIONS_CACHE;
+
+        $this->_sCodeKey = BX_DOL_TEMPLATE_CODE_KEY;
+        $this->_sMixKey = BX_DOL_TEMPLATE_MIX_KEY;
+        list(
+            $this->_sCode, 
+            $this->_sName, 
+            $this->_sSubPath
+        ) = self::retrieveCode($this->_sCodeKey, $this->_sMixKey, $this->_sRootPath);
+
+        $this->_iMix = 0;
+        if(is_array($this->_sCode))
+            list($this->_sCode, $this->_iMix) = $this->_sCode;
+
+        if(!$this->_sSubPath)
+            $this->_sSubPath = 'boonex/' . BX_DOL_TEMPLATE_DEFAULT_CODE . '/';
+
+        if(!file_exists(BX_DIRECTORY_PATH_MODULES . $this->_sSubPath)) // just for 8.0.0-A6 upgrade
+            $this->_sSubPath = 'boonex/uni/';
+
+        if(isset($_GET[$this->_sCodeKey])) {
+            if(BxDolPermalinks::getInstance()->redirectIfNecessary(array($this->_sCodeKey)))
+                exit;
+        }
+
+        $this->_sKeyWrapperHtml = '__';
+        $this->_sFolderHtml = '';
+        $this->_sFolderCss = 'css/';
+        $this->_sFolderImages = 'images/';
+        $this->_sFolderIcons = 'images/icons/';
+        $this->_aTemplates = array('html_tags', 'menu_item_addon', 'menu_item_addon_small', 'menu_item_addon_middle');
+
+        $this->addLocation('system', $this->_sRootPath, $this->_sRootUrl);
+
+        $this->addLocationJs('system_inc_js', BX_DIRECTORY_PATH_INC . 'js/' , BX_DOL_URL_ROOT . 'inc/js/');
+        $this->addLocationJs('system_inc_js_classes', BX_DIRECTORY_PATH_INC . 'js/classes/' , BX_DOL_URL_ROOT . 'inc/js/classes/');
+        $this->addLocationJs('system_plugins_public', BX_DIRECTORY_PATH_PLUGINS_PUBLIC, BX_DOL_URL_PLUGINS_PUBLIC);
+
+        $this->_bCacheEnable = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_enable') == 'on';
+        $this->_sCacheFolderUrl = '';
+        $this->_sCachePublicFolderUrl = BX_DOL_URL_CACHE_PUBLIC;
+        $this->_sCachePublicFolderPath = BX_DIRECTORY_PATH_CACHE_PUBLIC;
+        $this->_sCacheFilePrefix = "bx_templ_";
+
+        $this->_bImagesInline = getParam('sys_template_cache_image_enable') == 'on';
+        $this->_iImagesMaxSize = (int)getParam('sys_template_cache_image_max_size') * 1024;
+
+        $bArchive = getParam('sys_template_cache_compress_enable') == 'on';
+
+        $this->_bCssLess = true; //--- Less cannot be disabled for CSS.
+        $this->_bCssCache = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_css_enable') == 'on';
+        $this->_bCssMinify = $this->_bCssCache && getParam('sys_template_cache_minify_css_enable') == 'on';
+        $this->_bCssArchive = $this->_bCssCache && $bArchive;
+        $this->_sCssLessPrefix = $this->_sCacheFilePrefix . 'less_';
+        $this->_sCssCachePrefix = $this->_sCacheFilePrefix . 'css_';
+
+        $this->_bJsLess = false; //--- Less language isn't available for JS at all.
+        $this->_bJsCache = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_js_enable') == 'on';
+        $this->_bJsMinify = $this->_bJsCache && getParam('sys_template_cache_minify_js_enable') == 'on';
+        $this->_bJsArchive = $this->_bJsCache && $bArchive;
+        $this->_sJsCachePrefix = $this->_sCacheFilePrefix . 'js_';
+
+        $this->aPage = array();
+        $this->aPageContent = array();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolTemplate();
+            $GLOBALS['bxDolClasses'][__CLASS__]->init();
+        }
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    /**
+     * Retrieve template code and check whether it's associated with active template or not.
+     *
+     * @param string $sCodeKey template's code key.
+     * @param string $sMixKey template's mix key.
+     * @param string $sRootPath path to root directory.
+     */
+    public static function retrieveCode($sCodeKey = BX_DOL_TEMPLATE_CODE_KEY, $sMixKey = BX_DOL_TEMPLATE_MIX_KEY, $sRootPath = BX_DIRECTORY_PATH_ROOT)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $fCheckCode = function($sCode, $bSetCookie) use($sCodeKey, $sRootPath) {
+            if(empty($sCode) || !preg_match('/^[A-Za-z0-9_-]+$/', $sCode))
+                return false;
+
+            $aModule = BxDolModuleQuery::getInstance()->getModuleByUri($sCode);
+            if(empty($aModule) || !is_array($aModule) || (int)$aModule['enabled'] != 1 || !file_exists($sRootPath . 'modules/' . $aModule['path'] . 'data/template/'))
+                return false;
+
+            $oConfig = new BxDolModuleConfig($aModule);
+
+            $aResult = array(
+                $oConfig->getUri(), //--- Template module's URI is used as template Code. 
+                $oConfig->getName(),
+                $oConfig->getDirectory()
+            );
+
+            if(!$bSetCookie || bx_get('preview'))
+                return $aResult;
+
+            $aUrl = parse_url(BX_DOL_URL_ROOT);
+            $sPath = isset($aUrl['path']) && !empty($aUrl['path']) ? $aUrl['path'] : '/';
+
+            setcookie($sCodeKey, $sCode, time() + 60*60*24*365, $sPath);
+
+            return $aResult;
+        };
+
+        $fCheckMix = function($aResult, $iMix, $bSetCookie) use($sMixKey, $sRootPath, $oDb) {
+            list($sCode, $sName) = $aResult;
+            if(empty($sName) || empty($iMix))
+                return false;
+
+            $aMix = $oDb->getParamsMix($iMix);
+            if(empty($aMix) || !is_array($aMix) || $aMix['type'] != $sName)
+                return false;
+
+            if(!$bSetCookie)
+                return $iMix;
+
+            $aUrl = parse_url(BX_DOL_URL_ROOT);
+            $sPath = isset($aUrl['path']) && !empty($aUrl['path']) ? $aUrl['path'] : '/';
+
+            setcookie($sMixKey, $iMix, time() + 60*60*24*365, $sPath);
+
+            return $iMix;
+        };
+
+        $sCode = getParam('template');
+        if(empty($sCode))
+            $sCode = BX_DOL_TEMPLATE_DEFAULT_CODE;
+        $aResult = $fCheckCode($sCode, false);
+
+        //--- Check selected template in COOKIE(the lowest priority) ---//
+        $sCode = !empty($_COOKIE[$sCodeKey]) ? $_COOKIE[$sCodeKey] : '';
+        $aResultCheck = $fCheckCode($sCode, false);
+        if($aResultCheck !== false)
+            $aResult = $aResultCheck;
+
+        //--- Check selected template in GET(the highest priority) ---//
+        $sCode = !empty($_GET[$sCodeKey]) ? $_GET[$sCodeKey] : '';
+        $aResultCheck = $fCheckCode($sCode, true);
+        if($aResultCheck !== false)
+            $aResult = $aResultCheck;
+
+        if($aResult === false) 
+            return $aResult;
+
+        if(!is_array($aResult[0]))
+            $aResult[0] = array($aResult[0]);
+
+        $iMixDefault = !empty($aResult[1]) ? (int)getParam($aResult[1] . '_default_mix') : 0;
+
+        //--- Check selected mix in COOKIE(the lowest priority) ---//
+        $iMix = !empty($_COOKIE[$sMixKey]) ? (int)$_COOKIE[$sMixKey] : 0;
+        $iResultCheck = $fCheckMix($aResult, $iMix, false);
+        if($iResultCheck !== false) {
+            $aMix = $oDb->getParamsMix($iMix);
+            if((int)$aMix['published'] == 0 && $iMix != $iMixDefault) {
+                $aUrl = parse_url(BX_DOL_URL_ROOT);
+                $sPath = isset($aUrl['path']) && !empty($aUrl['path']) ? $aUrl['path'] : '/';
+
+                setcookie($sMixKey, '', time() - 96 * 3600, $sPath);
+                unset($_COOKIE[$sMixKey]);
+            }
+            else
+                $aResult[0][1] = $iResultCheck;
+        }
+
+        //--- Check selected mix in GET(the highest priority) ---//
+        $iMix = !empty($_GET[$sMixKey]) ? (int)$_GET[$sMixKey] : 0;
+        $iResultCheck = $fCheckMix($aResult, $iMix, true);
+        if($iResultCheck !== false)
+            $aResult[0][1] = $iResultCheck;
+
+        //--- Get default mix for currently selected template ---//
+        if(empty($aResult[0][1]) && !empty($iMixDefault)) {
+            $iResultCheck = $fCheckMix($aResult, $iMixDefault, false);
+            if($iResultCheck !== false)
+                $aResult[0][1] = $iResultCheck;
+        }
+
+        if(is_array($aResult[0]) && count($aResult[0]) == 1)
+           $aResult[0] = $aResult[0][0];
+
+        return $aResult;
+    }
+
+    public function getClassName()
+    {
+        return get_class($this);
+    }
+
+    public static function getColorCode($mixedName = false, $fOpacity = false)
+    {
+        $aClasses = array_keys(self::$_aColors);
+
+        if($mixedName === false || (is_string($mixedName) && !is_numeric($mixedName) && !in_array($mixedName, $aClasses)))
+            $mixedName = $aClasses[rand(0, count($aClasses) - 1)];
+        else if(is_numeric($mixedName))
+            $mixedName = $aClasses[(int)$mixedName % count($aClasses)];
+
+        $aColor = self::$_aColors[$mixedName];
+        if($fOpacity !== false && is_numeric($fOpacity))
+            $aColor[] = $fOpacity;
+
+        return $aColor;
+    }
+
+    public static function getColorClass($sType = BX_DOL_COLOR_FT, $sName = '')
+    {
+        $aClasses = array_keys(self::$_aColors);
+
+        if(empty($sName) || !in_array($sName, $aClasses))
+            $sName = $aClasses[rand(0, count($aClasses) - 1)];
+
+        $sPrefix = '';
+        switch ($sType) {
+            case BX_DOL_COLOR_FT:
+                $sPrefix = 'col-';
+                break;
+
+            case BX_DOL_COLOR_BG:
+                $sPrefix = 'bg-col-';
+                break;
+        }
+
+        return $sPrefix . $sName;
+    }
+
+    /**
+     * Load templates.
+     */
+    function loadTemplates()
+    {
+        $aResult = array();
+        foreach($this->_aTemplates as $sName)
+            $aResult[$sName] = $this->getHtml($sName . '.html');
+        $this->_aTemplates = $aResult;
+    }
+    /**
+     * Initialize template engine.
+     * Note. The method is executed with the system, you shouldn't execute it in your subclasses.
+     */
+    function init()
+    {
+        $this->loadTemplates();
+
+        //--- Load page elements related static variables ---//
+        $this->aPage = array(
+            'name_index' => BX_PAGE_DEFAULT,
+        	'type' => BX_PAGE_TYPE_DEFAULT,
+            'header' => '',
+            'header_text' => '',
+            'keywords' => array(),
+            'location' => array(),
+            'description'  => '',
+            'robots' => '',
+            'css_name' => array(),
+            'css_compiled' => array(),
+            'css_system' => array(),
+            'css_async' => array(),
+            'js_name' => array(),
+            'js_compiled' => array(),
+            'js_system' => array(),
+            'js_options' => array(),
+            'js_translations' => array(),
+            'js_images' => array(),
+            'injections' => array()
+        );
+
+        //--- Load default CSS, JS, etc ---//
+        BxDolPreloader::getInstance()->perform($this);
+
+        //--- Load injection's cache ---//
+        if (getParam('sys_db_cache_enable')) {
+            $oDb = BxDolDb::getInstance();
+            $oCache = $oDb->getDbCacheObject();
+            $sCacheKey = $oDb->genDbCacheKey($this->_sInjectionsCache);
+
+            $aInjections = $oCache->getData($sCacheKey);
+            if ($aInjections === null) {
+                $aInjections = $this->getInjectionsData();
+                $oCache->setData ($sCacheKey, $aInjections);
+            }
+        } 
+        else
+            $aInjections = $this->getInjectionsData();
+
+        $this->aPage['injections'] = $aInjections;
+
+        bx_import('BxTemplConfig'); // TODO: for some reason autoloader isn't working here....
+        $this->_oTemplateConfig = BxTemplConfig::getInstance();
+
+        bx_import('BxTemplFunctions');
+        $this->_oTemplateFunctions = BxTemplFunctions::getInstance($this);
+    }
+
+    protected function getInjectionsData ()
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $aInjections = $oDb->getAll("SELECT `page_index`, `name`, `key`, `type`, `data`, `replace` FROM `" . $this->_sInjectionsTable . "` WHERE `active`='1'");
+        if (!$aInjections)
+            return array();
+
+        foreach ($aInjections as $aInjection)
+            $aInjections['page_' . $aInjection['page_index']][$aInjection['key']][] = $aInjection;
+
+        return $aInjections;
+    }
+
+    /**
+     * Set page name index
+     * @param int $i name index
+     */
+    function setPageNameIndex($i)
+    {
+        $this->aPage['name_index'] = $i;
+    }
+
+    /**
+     * Get page name index
+     * @return int $i name index
+     */
+    function getPageNameIndex()
+    {
+        return isset($this->aPage['name_index']) ? (int)$this->aPage['name_index'] : 0;
+    }
+
+	/**
+     * Set page type
+     * @param int $i page type
+     */
+    function setPageType($a)
+    {
+        $this->aPage['type'] = $a;
+    }
+
+    /**
+     * Get page type
+     * @return int $i page type
+     */
+    function getPageType()
+    {
+        return isset($this->aPage['type']) ? (int)$this->aPage['type'] : BX_PAGE_TYPE_DEFAULT;
+    }
+
+    /**
+     * Set page header
+     * @param string $s page header
+     */
+    function setPageHeader($s)
+    {
+        $this->aPage['header'] = $s;
+    }
+
+    /**
+     * Get page header
+     * @return string $s page header
+     */
+    function getPageHeader()
+    {
+        return $this->aPage['header'];
+    }
+
+    /**
+     * Set page params. Available page params are: name_index, header
+     * @param array $a page params
+     */
+    function setPageParams($a)
+    {
+        if (!empty($this->aPage))
+            $this->aPage = array_merge($this->aPage, $a);
+        else
+            $this->aPage = $a;
+    }
+
+    /**
+     * Get page params.
+     * @return array $a page params
+     */
+    function getPageParams()
+    {
+        return $this->aPage;
+    }
+
+    /**
+     * Set page description.
+     *
+     * @param string $sDescription necessary page description.
+     */
+    function setPageDescription($sDescription)
+    {
+        $this->aPage['description'] = $sDescription;
+    }
+
+    /**
+     * Set page meta robots.
+     *
+     * @param string $s page meta robots.
+     */
+    function setPageMetaRobots($s)
+    {
+        $this->aPage['robots'] = $s;
+    }
+
+    /**
+     * Set page content for some variable.
+     * @param string $sVar     name of content variable
+     * @param string $sContent content for $sVar variable
+     * @param int    $iIndex   optional page index, default is index which was set before with @see setPageNameIndex function, or 0
+     */
+    function setPageContent($sVar, $sContent, $iIndex = false)
+    {
+        $i = false !== $iIndex ? $iIndex : $this->getPageNameIndex();
+        $this->aPageContent[$i][$sVar] = $sContent;
+    }
+
+    /**
+     * Get page content for some variable.
+     * @param  string $sVar   name of content variable
+     * @param  int    $iIndex optional page index, default is index which was set before with @see setPageNameIndex function, or 0
+     * @return string page content for some variable or for the whole page.
+     */
+    function getPageContent($sVar = false, $iIndex = false)
+    {
+        $i = false !== $iIndex ? $iIndex : $this->getPageNameIndex();
+        return false !== $sVar ? $this->aPageContent[$i][$sVar] : $this->aPageContent[$i];
+    }
+
+    /**
+     * Get currently active template name.
+     *
+     * @return string template's name.
+     */
+    function getName()
+    {
+        return $this->_sName;
+    }
+
+    /**
+     * Get currently active template code.
+     *
+     * @return string template's code.
+     */
+    function getCode()
+    {
+        return $this->_sCode;
+    }
+
+    /**
+     * Get currently active template mix.
+     *
+     * @return integer template's mix.
+     */
+    function getMix()
+    {
+        return $this->_iMix;
+    }
+
+    /**
+     * Get currently active template path.
+     *
+     * @return string template's path.
+     */
+    function getPath()
+    {
+        return $this->_sSubPath;
+    }
+
+    /**
+     * Set page title.
+     * @deprecated use setPageHeader
+     *
+     * @param string $sTitle necessary page title.
+     */
+    function setPageTitle($sTitle)
+    {
+        $this->setPageHeader($sTitle);
+    }
+
+    /**
+     * Set page's main box title.
+     * @deprecated use setPageParams
+     *
+     * @param string $sTitle necessary page's main box title.
+     */
+    function setPageMainBoxTitle($sTitle)
+    {
+        $this->setPageParams(array('header_text' => $sTitle));
+    }
+
+    /**
+     * Check whether location exists or not.
+     * @param string $sKey - location's unique key.
+     */
+    function isLocation($sKey)
+    {
+        return isset($this->_aLocations[$sKey]);
+    }
+
+    /**
+     * Add location in array of locations.
+     * Note. Location is the path/url to folder where 'templates' folder is stored.
+     *
+     * @param string $sKey          - location's unique key.
+     * @param string $sLocationPath - location's path. For modules: '[path_to_script]/modules/[vendor_name]/[module_name]/'
+     * @param string $sLocationUrl  - location's url. For modules: '[url_to_script]/modules/[vendor_name]/[module_name]/'
+     */
+    function addLocation($sKey, $sLocationPath, $sLocationUrl)
+    {
+        $this->_aLocations[$sKey] = array(
+            'path' => $sLocationPath,
+            'url' => $sLocationUrl,
+        );
+
+        return $sKey;
+    }
+    /**
+     * Add dynamic location.
+     *
+     * @param  string   $sLocationPath - location's path. For modules: '[path_to_script]/modules/[vendor_name]/[module_name]/'
+     * @param  string   $sLocationUrl  - location's url. For modules: '[url_to_script]/modules/[vendor_name]/[module_name]/'
+     * @return location key. Is needed to remove the location.
+     */
+    function addDynamicLocation($sLocationPath, $sLocationUrl)
+    {
+        $sLocationKey = time() . mt_rand();
+        $this->addLocation($sLocationKey, $sLocationPath, $sLocationUrl);
+
+        return $sLocationKey;
+    }
+    /**
+     * Remove location from array of locations.
+     * Note. Location is the path/url to folder where templates are stored.
+     *
+     * @param string $sKey - location's    unique key.
+     */
+    function removeLocation($sKey)
+    {
+        if(isset($this->_aLocations[$sKey]))
+           unset($this->_aLocations[$sKey]);
+    }
+	/**
+     * Check whether JS location exists or not.
+     * @param string $sKey - JS location's unique key.
+     */
+    function isLocationJs($sKey)
+    {
+        return isset($this->_aLocationsJs[$sKey]);
+    }
+    /**
+     * Add JS location in array of JS locations.
+     * Note. Location is the path/url to folder where JS files are stored.
+     *
+     * @param string $sKey          - location's    unique key.
+     * @param string $sLocationPath - location's path. For modules: '[path_to_script]/modules/[vendor_name]/[module_name]/js/'
+     * @param string $sLocationUrl  - location's url. For modules: '[url_to_script]/modules/[vendor_name]/[module_name]/js/'
+     */
+    function addLocationJs($sKey, $sLocationPath, $sLocationUrl)
+    {
+        $this->_aLocationsJs[$sKey] = array(
+            'path' => $sLocationPath,
+            'url' => $sLocationUrl
+        );
+
+        return $sKey;
+    }
+    /**
+     * Add dynamic JS location.
+     *
+     * @param  string   $sLocationPath - location's path. For modules: '[path_to_script]/modules/[vendor_name]/[module_name]/'
+     * @param  string   $sLocationUrl  - location's url. For modules: '[url_to_script]/modules/[vendor_name]/[module_name]/'
+     * @return location key. Is needed to remove the location.
+     */
+    function addDynamicLocationJs($sLocationPath, $sLocationUrl)
+    {
+        $sLocationKey = time() . mt_rand();
+        $this->addLocationJs($sLocationKey, $sLocationPath, $sLocationUrl);
+
+        return $sLocationKey;
+    }
+    /**
+     * Remove JS location from array of locations.
+     * Note. Location is the path/url to folder where templates are stored.
+     *
+     * @param string $sKey - JS location's    unique key.
+     */
+    function removeLocationJs($sKey)
+    {
+        if(isset($this->_aLocationsJs[$sKey]))
+           unset($this->_aLocationsJs[$sKey]);
+    }
+    /**
+     * Add Option in JS output.
+     *
+     * @param mixed $mixedName option's name or an array of options' names.
+     */
+    function addJsOption($mixedName)
+    {
+        if(is_string($mixedName))
+            $mixedName = array($mixedName);
+
+        foreach($mixedName as $sName)
+            $this->aPage['js_options'][$sName] = getParam($sName);
+    }
+    /**
+     * Add language translation for key in JS output.
+     *
+     * @param mixed $mixedKey language key or an array of keys.
+     */
+    function addJsTranslation($mixedKey, $bDynamic = false)
+    {
+        if(is_string($mixedKey))
+            $mixedKey = array($mixedKey);
+
+        foreach($mixedKey as $sKey)
+            $this->aPage['js_translations'][$sKey] = _t($sKey, '{0}', '{1}');
+
+        return $bDynamic ? $this->_processJsTranslations() : '';
+    }
+    /**
+     * Add image in JS output.
+     *
+     * @param array $aImages an array of image descriptors.
+     *                       The descriptor is a key/value pear in the array of descriptors.
+     */
+    function addJsImage($aImages)
+    {
+        if(!is_array($aImages))
+            return;
+
+        foreach($aImages as $sKey => $sFile) {
+            $sUrl = $this->getImageUrl($sFile);
+            if(empty($sUrl))
+                continue;
+
+            $this->aPage['js_images'][$sKey] = $sUrl;
+        }
+    }
+    /**
+     * Add icon in JS output.
+     *
+     * @param array $aIcons an array of icons descriptors.
+     *                      The descriptor is a key/value pear in the array of descriptors.
+     */
+    function addJsIcon($aIcons)
+    {
+        if(!is_array($aIcons))
+            return;
+
+        foreach($aIcons as $sKey => $sFile) {
+            $sUrl = $this->getIconUrl($sFile);
+            if(empty($sUrl))
+                continue;
+
+            $this->aPage['js_images'][$sKey] = $sUrl;
+        }
+    }
+	/**
+	 * Add CSS style.
+	 *
+	 * @param string $sName CSS class name.
+	 * @param string $sContent CSS class styles.
+	 */
+	function addCssStyle($sName, $sContent)
+	{
+		$this->aPage['css_styles'][$sName] = $sContent;
+	}
+    /**
+     * Set page keywords.
+     *
+     * @param mixed  $mixedKeywords necessary page keywords(string - single keyword, array - an array of keywords).
+     * @param string $sDevider      - string devider.
+     */
+    function addPageKeywords($mixedKeywords, $sDevider = ',')
+    {
+        if(is_string($mixedKeywords))
+            $mixedKeywords = strpos($mixedKeywords, $sDevider) !== false ? explode($sDevider, $mixedKeywords) : array($mixedKeywords);
+
+        foreach($mixedKeywords as $iKey => $sValue)
+            $mixedKeywords[$iKey] = trim($sValue);
+
+        $this->aPage['keywords'] = isset($this->aPage['keywords']) && is_array($this->aPage['keywords']) ? array_merge($this->aPage['keywords'], $mixedKeywords) : $mixedKeywords;
+    }
+    /**
+     * Set page locatoin coordinates.
+     *
+     * @param $fLat latitude
+     * @param $fLng longitude
+     */
+    function addPageMetaLocation($fLat, $fLng, $sCountryCode)
+    {
+        $this->aPage['location'] = array('lat' => $fLat, 'lng' => $fLng, 'country' => $sCountryCode);
+    }
+    /**
+     * Set page meta image.
+     *
+     * @param $sImageUrl meta image url
+     */
+    function addPageMetaImage($sImageUrl)
+    {
+        $this->aPage['image'] = $sImageUrl;
+    }
+    /**
+     * Set page rss link.
+     *
+     * @param $sTitle - rss feed title
+     * @param $sUrl - rss feed URL
+     */
+    function addPageRssLink($sTitle, $sUrl)
+    {
+        if (!isset($this->aPage['rss']))
+            $this->aPage['rss'] = array('title' => $sTitle, 'url' => $sUrl);
+        else
+            $this->aPage['rss'] = false;
+    }
+    /**
+     * Returns page meta info, like meta keyword, meta description, location, etc
+     */
+    function getMetaInfo()
+    {
+        $sRet = '';
+
+        // general meta tags
+        if (!empty($this->aPage['keywords']) && is_array($this->aPage['keywords']))
+            $sRet .= '<meta name="keywords" content="' . bx_html_attribute(implode(',', $this->aPage['keywords'])) . '" />';
+
+        $bDescription = !empty($this->aPage['description']) && is_string($this->aPage['description']);
+        if ($bDescription)
+            $sRet .= '<meta name="description" content="' . bx_html_attribute($this->aPage['description']) . '" />';
+
+        // location
+        if (!empty($this->aPage['location']) && isset($this->aPage['location']['lat']) && isset($this->aPage['location']['lng']) && isset($this->aPage['location']['country']))
+            $sRet .= '
+                <meta name="ICBM" content="' . $this->aPage['location']['lat'] . ';' . $this->aPage['location']['lng'] . '" />
+                <meta name="geo.position" content="' . $this->aPage['location']['lat'] . ';' . $this->aPage['location']['lng'] . '" />
+                <meta name="geo.region" content="' . bx_html_attribute($this->aPage['location']['country']) . '" />';
+
+
+        // facebook / twitter
+        $bPageImage = !empty($this->aPage['image']);
+        $sRet .= '<meta name="twitter:card" content="' . ($bPageImage ? 'summary_large_image' : 'summary') . '" />';
+        if ($bPageImage)
+            $sRet .= '<meta property="og:image" content="' . $this->aPage['image'] . '" />';
+        $sRet .= '
+			<meta property="og:title" content="' . (isset($this->aPage['header']) ? bx_html_attribute(strip_tags($this->aPage['header'])) : '') . '" />
+			<meta property="og:description" content="' . ($bDescription ? bx_html_attribute($this->aPage['description']) : '') . '" />';
+
+        // Smart App Banner
+        if (getParam('smart_app_banner') && false === strpos($_SERVER['HTTP_USER_AGENT'], 'UNAMobileApp')) {
+            if ($sAppIdIOS = getParam('smart_app_banner_ios_app_id'))
+                $sRet .= '<meta name="apple-itunes-app" content="app-id=' . $sAppIdIOS . '" />';
+        }
+
+        // RSS
+        $oFunctions = BxTemplFunctions::getInstance();
+        $sRet .= $oFunctions->getManifests();
+        $sRet .= $oFunctions->getMetaIcons();
+
+        if (!empty($this->aPage['rss']) && !empty($this->aPage['rss']['url']))
+            $sRet .= '<link rel="alternate" type="application/rss+xml" title="' . bx_html_attribute($this->aPage['rss']['title'], BX_ESCAPE_STR_QUOTE) . '" href="' . $this->aPage['rss']['url'] . '" />';
+
+        return $sRet;
+    }
+    /**
+     * Get template, which was loaded earlier.
+     * @see method this->loadTemplates and field this->_aTemplates
+     *
+     * @param  string $sName - template name.
+     * @return string template's content.
+     */
+    public function getTemplate($sName)
+    {
+        return $this->_aTemplates[$sName];
+    }
+
+    /**
+     * Get template functions object.
+     * @see BxBaseFunctions
+     */
+    public function getTemplateFunctions()
+    {
+        return $this->_oTemplateFunctions;
+    }
+
+    /**
+     * Get image MIME type.
+     * 
+     * @param string $sExtension - image file's extension.
+     * @return string with MIME type. 
+     */
+    function getImageMimeType($sExtension)
+    {
+    	$sExtension = strtolower($sExtension);
+    
+    	$sResult = '';
+    	switch($sExtension) {
+    		case 'svg':
+    			$sResult = 'svg+xml';
+    			break;
+    
+    		default:
+    			$sResult = $sExtension;
+    	}
+    
+    	return 'data:image/' . $sResult;
+    }
+
+    /**
+     * Get icon template in dependence of a value, provided in $mixedId.
+     * 
+     * @param  mixed $mixedId numeric id from Storage, string with template's file name or string with font icon.
+     */
+	public function getIcon($mixedId, $aParams = array())
+    {
+        return $this->_getImage('icon', $mixedId, $aParams);
+    }
+
+    /**
+     * Get image template in dependence of a value, provided in $mixedId.
+     * 
+     * @param  mixed $mixedId numeric id from Storage, string with template's file name or string with font icon.
+     */
+    public function getImage($mixedId, $aParams = array())
+    {
+        return $this->_getImage('image', $mixedId, $aParams);
+    }
+
+	protected function _getImage($sType, $mixedId, $aParams = array())
+    {
+        $sUrl = "";
+        $aType2Method = array('image' => 'getImageUrl', 'icon' => 'getIconUrl');
+
+        //--- Check in System Storage.
+        if(is_numeric($mixedId) && (int)$mixedId > 0) {
+        	$sStorage = BX_DOL_STORAGE_OBJ_IMAGES;
+        	if(!empty($aParams['storage'])) {
+        		$sStorage = $aParams['storage'];
+        		unset($aParams['storage']);
+        	}
+
+            if(($sResult = BxDolStorage::getObjectInstance($sStorage)->getFileUrlById((int)$mixedId)) !== false)
+                $sUrl = $sResult;
+        }
+
+        //--- Check in template folders.
+        if($sUrl == "" && is_string($mixedId) && strpos($mixedId, '.') !== false)
+            $sUrl = $this->{$aType2Method[$sType]}($mixedId);
+
+        if($sUrl != "")
+            return $this->parseImage($sUrl, array(
+                'class' => isset($aParams['class']) && !empty($aParams['class']) ? $aParams['class'] : '',
+            	'alt' => isset($aParams['alt']) && !empty($aParams['alt']) ? $aParams['alt'] : ''
+            ));
+
+        //--- Use iconic font.
+        return $this->parseIcon($mixedId, $aParams);
+    }
+
+    /**
+     * Get full URL for the icon.
+     *
+     * @param  string $sName    icon's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getIconUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sContent = "";
+        if(($sContent = $this->_getInlineData('icon', $sName, $sCheckIn)) !== false)
+            return $sContent;
+
+        return $this->_getAbsoluteLocation('url', $this->_sFolderIcons, $sName, $sCheckIn);
+    }
+    /**
+     * Get absolute Path for the icon.
+     *
+     * @param  string $sName    - icon's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string absolute path.
+     */
+    function getIconPath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderIcons, $sName, $sCheckIn);
+    }
+    /**
+     * Get full URL for the image.
+     *
+     * @param  string $sName    - images's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getImageUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sContent = "";
+        if(($sContent = $this->_getInlineData('image', $sName, $sCheckIn)) !== false)
+            return $sContent;
+
+        return $this->_getAbsoluteLocation('url', $this->_sFolderImages, $sName, $sCheckIn);
+    }
+    /**
+     * Get absolute Path for the image.
+     *
+     * @param  string $sName    - image's file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string absolute path.
+     */
+    function getImagePath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderImages, $sName, $sCheckIn);
+    }
+    /**
+     * Get full URL of CSS file.
+     *
+     * @param  string $sName    - CSS file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getCssUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('url', $this->_sFolderCss, $sName, $sCheckIn);
+    }
+    /**
+     * Get full Path of CSS file.
+     *
+     * @param  string $sName    - CSS file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full URL.
+     */
+    function getCssPath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderCss, $sName, $sCheckIn);
+    }
+    /**
+     * Get full URL of JS file.
+     *
+     * @param  string $sName    - JS file name.
+     * @return string full URL.
+     */
+    function getJsUrl($sName)
+    {
+        return $this->_getAbsoluteLocationJs('url', $sName);
+    }
+    /**
+     * Get full Path of JS file.
+     *
+     * @param  string $sName    - JS file name.
+     * @return string full URL.
+     */
+    function getJsPath($sName)
+    {
+        return $this->_getAbsoluteLocationJs('path', $sName);
+    }
+    /**
+     * Get full URL of Template (HTML) file.
+     *
+     * @param  string $sName    - Template file name.
+     * @return string full URL.
+     */
+    function getTemplateUrl($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('url', $this->_sFolderHtml, $sName, $sCheckIn);
+    }
+    /**
+     * Get full Path of JS file.
+     *
+     * @param  string $sName    - JS file name.
+     * @return string full URL.
+     */
+    function getTemplatePath($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        return $this->_getAbsoluteLocation('path', $this->_sFolderHtml, $sName, $sCheckIn);
+    }
+
+    /**
+     * Get menu.
+     * @param $s menu object name
+     * @return html or empty string
+     */
+    function getMenu ($s)
+    {
+        $oMenu = BxDolMenu::getObjectInstance($s);
+        return $oMenu ? $oMenu->getCode () : '';
+    }
+
+    /**
+     * Get content of HTML file.
+     *
+     * @param  string $sName    - HTML file name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string full content of the file and false on failure.
+     */
+    function getHtml($sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sAbsolutePath = $this->_getAbsoluteLocation('path', $this->_sFolderHtml, $sName, $sCheckIn);
+        return !empty($sAbsolutePath) ? trim(file_get_contents($sAbsolutePath)) : false;
+    }
+
+    /**
+     * Parse HTML template. Search for the template with accordance to it's file name.
+     *
+     * @see allows to use cache.
+     *
+     * @param  string $sName               - HTML file name.
+     * @param  array  $aVariables          - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed  $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(left, right) otherwise).
+     * @param  string $sCheckIn            where the content would be searched(base, template, both)
+     * @return string the result of operation.
+     */
+    function parseHtmlByName($sName, $aVariables, $mixedKeyWrapperHtml = null, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginTemplate($sName, $sRand = time().rand());
+
+        if (($sContent = $this->getCached($sName, $aVariables, $mixedKeyWrapperHtml, $sCheckIn)) !== false) {
+            if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endTemplate($sName, $sRand, $sRet, true);
+            return $sContent;
+        }
+
+        $sRet = '';
+        if (($sContent = $this->getHtml($sName, $sCheckIn)) !== false)
+            $sRet = $this->_parseContent($sContent, $aVariables, $mixedKeyWrapperHtml);
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endTemplate($sName, $sRand, $sRet, false);
+
+        return $sRet;
+    }
+    /**
+     * Parse HTML template.
+     *
+     * @see Doesn't allow to use cache.
+     *
+     * @param  string $sContent            - HTML file content.
+     * @param  array  $aVariables          - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed  $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(left, right) otherwise).
+     * @return string the result of operation.
+     */
+    function parseHtmlByContent($sContent, $aVariables, $mixedKeyWrapperHtml = null)
+    {
+        if(empty($sContent))
+            return "";
+
+        return $this->_parseContent($sContent, $aVariables, $mixedKeyWrapperHtml);
+    }
+    /**
+     * Parse earlier loaded HTML template.
+     *
+     * @see Doesn't allow to use cache.
+     *
+     * @param  string $sName      - template name.
+     * @param  array  $aVariables - key/value pairs. Key should be the same as template's key, excluding prefix and postfix.
+     * @return string the result of operation.
+     * @see $this->_aTemplates
+     */
+    function parseHtmlByTemplateName($sName, $aVariables, $mixedKeyWrapperHtml = null)
+    {
+        if(!isset($this->_aTemplates[$sName]) || empty($this->_aTemplates[$sName]))
+            return "";
+
+        return $this->_parseContent($this->_aTemplates[$sName], $aVariables, $mixedKeyWrapperHtml);
+    }
+    /**
+     * Parse page HTML template. Search for the page's template with accordance to it's file name.
+     *
+     * @see allows to use cache.
+     *
+     * @param  string $sName      - HTML file name.
+     * @param  array  $aVariables - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @return string the result of operation.
+     */
+    function parsePageByName($sName, $aVariables)
+    {
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginPage($sName);
+
+        $sContent = $this->parseHtmlByName($sName, $aVariables, $this->_sKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BOTH);
+        if(empty($sContent)) {
+        	$aType = BxDolPageQuery::getPageType($this->getPageType());
+        	if(!empty($aType) && is_array($aType))
+        		$sContent = $this->parseHtmlByName($aType['template'], $aVariables, $this->_sKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BOTH);
+        }
+        if(empty($sContent))
+            $sContent = $this->parseHtmlByName('default.html', $aVariables, $this->_sKeyWrapperHtml, BX_DOL_TEMPLATE_CHECK_IN_BOTH);
+
+        //--- Add CSS and JS at the very last ---//
+        if(strpos($sContent, '<bx_include_css_styles />') !== false) {
+            $aStyles = array(
+                'display' => 'none !important'
+            );
+
+            if(isLogged())
+                $this->addCssStyle('.bx-hide-when-logged-in', $aStyles);
+            else
+                $this->addCssStyle('.bx-hide-when-logged-out', $aStyles);
+
+            $sContent = str_replace('<bx_include_css_styles />', $this->includeCssStyles(), $sContent);
+        }
+
+        if(strpos($sContent , '<bx_include_css_system />') !== false) {
+            $sContent = str_replace('<bx_include_css_system />', $this->includeFiles('css', true), $sContent);
+        }
+
+        if(strpos($sContent , '<bx_include_css />') !== false) {
+            if (!empty($this->aPage['css_name']))
+                $this->addCss($this->aPage['css_name']);
+            $sContent = str_replace('<bx_include_css />', $this->includeFiles('css'), $sContent);
+        }
+        
+        if(strpos($sContent , '<bx_include_js_system />') !== false) {
+            $sContent = str_replace('<bx_include_js_system />', $this->includeFiles('js', true), $sContent);
+        }
+
+        if(strpos($sContent , '<bx_include_js />') !== false) {
+            if (!empty($this->aPage['js_name']))
+                $this->addJs($this->aPage['js_name']);
+            $sContent = str_replace('<bx_include_js />', $this->includeFiles('js') . $this->includeCssAsync(), $sContent);
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endPage($sContent);
+
+        return $sContent;
+    }
+    /**
+     * Parse system keys.
+     *
+     * @param  string $sKey key
+     * @return string value associated with the key.
+     */
+    function parseSystemKey($sKey, $mixedKeyWrapperHtml = null, $bProcessInjection = true)
+    {
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        $sRet = '';
+        switch( $sKey ) {
+            case 'page_charset':
+                $sRet = 'UTF-8';
+                break;
+            case 'page_robots':
+                if(!empty($this->aPage['robots']) && is_string($this->aPage['robots']))
+                    $sRet = '<meta name="robots" content="' . bx_html_attribute($this->aPage['robots']) . '" />';
+                break;
+            case 'meta_info':
+                $sRet = $this->getMetaInfo();
+                break;
+            case 'page_header':
+                if(isset($this->aPage['header']))
+                    $sRet = bx_process_output(strip_tags($this->aPage['header']));
+                break;
+            case 'page_header_text':
+                if(isset($this->aPage['header_text']))
+                    $sRet = bx_process_output($this->aPage['header_text']);
+                break;
+            case 'page_width':
+                if (false === strpos($this->_oTemplateConfig->aLessConfig['bx-page-width'], 'px'))
+                    $sRet = BX_DOL_PAGE_WIDTH;
+                else
+                    $sRet = $this->_oTemplateConfig->aLessConfig['bx-page-width'];
+                break;
+            case 'system_injection_head':
+                $sRet = $this->_oTemplateFunctions->getInjectionHead();
+                break;
+            case 'system_injection_header':
+                $sRet = $this->_oTemplateFunctions->getInjectionHeader();
+                break;
+            case 'system_injection_footer':
+                $sRet = $this->_oTemplateFunctions->getInjectionFooter();
+                break;
+            case 'lang':
+                $sRet = bx_lang_name();
+                break;
+            case 'lang_direction':
+                $sRet = bx_lang_direction();
+                break;
+            case 'lang_country':
+                if (!($sRet = BxDolLanguages::getInstance()->getLangCountryCode()))
+                    $sRet = bx_lang_name();
+                break;                
+            case 'main_logo':
+                $sRet = BxTemplFunctions::getInstance()->getMainLogo();
+                break;
+            case 'informer':
+                $oInformer = BxDolInformer::getInstance($this);
+                $sRet = $oInformer ? $oInformer->display() : '';
+                break;
+            case 'cover':
+            	$oCover = BxDolCover::getInstance($this);
+            	if(!$oCover)
+                    break;
+
+            	$bCover = true;
+            	$oPage = BxDolPage::getObjectInstanceByURI();
+            	if($oPage !== false) {
+                    $bCover = $oPage->isPageCover();
+                    if($bCover && !$oCover->isCover()) {
+                        $aCover = $oPage->getPageCoverImage();
+                        if(!empty($aCover))
+                            $oCover->setCoverImageUrl($aCover);
+                    }
+            	}
+
+                $sRet = $bCover ? $oCover->display() : $oCover->displayEmpty();
+                break;
+            case 'dol_images':
+                $sRet = $this->_processJsImages();
+                break;
+            case 'dol_lang':
+                $sRet = $this->_processJsTranslations();
+                break;
+            case 'dol_options':
+                $sRet = $this->_processJsOptions();
+                break;
+            case 'copyright':
+                $sRet = _t( '_copyright', date('Y') ) . getVersionComment();
+                break;
+            case 'copyright_attr':
+                $sRet = bx_html_attribute(_t('_copyright', date('Y')));
+                break;
+            case 'extra_js':
+                $sRet = empty($this->aPage['extra_js']) ? '' : $this->aPage['extra_js'];
+                break;
+            case 'is_profile_page':
+                $sRet = (defined('BX_PROFILE_PAGE')) ? 'true' : 'false';
+                break;
+			case 'system_js_requred':
+                $sRet = _t('_sys_javascript_requred');
+                break;
+            default:
+                $sRet = ($sTemplAdd = BxTemplFunctions::getInstance()->TemplPageAddComponent($sKey)) !== false ? $sTemplAdd : $aKeyWrappers['left'] . $sKey . $aKeyWrappers['right'];
+        }
+
+        if($bProcessInjection)
+            $sRet = $this->processInjection($this->getPageNameIndex(), $sKey, $sRet);
+
+        return $sRet;
+    }
+
+    /**
+     * Parse tag <A>
+     * 
+     * @param string $sLink link URL
+     * @param string $sContent link content
+     * @param array $aAttrs an array of key => value pairs
+     */
+    function parseLink($sLink, $sContent, $aAttrs = array())
+    {
+        $sAttrs = '';
+        foreach($aAttrs as $sKey => $sValue)
+            $sAttrs .= ' ' . $sKey . '="' . bx_html_attribute($sValue) . '"';
+
+        return '<a href="' . $sLink . '"' . $sAttrs . '>' . $sContent . '</a>';
+    }
+
+    /**
+     * Parse tag <BUTTON>
+     * 
+     * @param string $sContent link content
+     * @param array $aAttrs an array of key => value pairs
+     */
+    function parseButton($sContent, $aAttrs = array())
+    {
+        $sAttrs = '';
+        foreach($aAttrs as $sKey => $sValue)
+            $sAttrs .= ' ' . $sKey . '="' . bx_html_attribute($sValue) . '"';
+
+        return '<button' . $sAttrs . '>' . $sContent . '</button>';
+    }
+
+    /**
+     * Parse tag <IMG>
+     * 
+     * @param string $sLink URL to image source 
+     * @param array $aAttrs an array of key => value pairs
+     */
+    function parseImage($sLink, $aAttrs = array())
+    {
+        $sAttrs = '';
+        foreach($aAttrs as $sKey => $sValue)
+            $sAttrs .= ' ' . $sKey . '="' . bx_html_attribute($sValue) . '"';
+
+        return '<img src="' . $sLink . '"' . $sAttrs . ' />';
+    }
+
+    /**
+     * Parse font based icon in <I> tag
+     * 
+     * @param string $sName font icon name
+     * @param array $aAttrs an array of key => value pairs
+     */
+    function parseIcon($sName, $aAttrs = array())
+    {
+        if(!empty($aAttrs['class'])) {
+            $sName .= ' ' . $aAttrs['class'];
+            unset($aAttrs['class']);
+        }
+
+        $sAttrs = '';
+        foreach($aAttrs as $sKey => $sValue)
+            $sAttrs .= ' ' . $sKey . '="' . bx_html_attribute($sValue) . '"';
+
+        return '<i class="sys-icon ' . $sName . '"' . $sAttrs . '></i>';
+    }
+
+    function getCacheFilePrefix($sType)
+    {
+    	$sResult = '';
+    	switch($sType) {
+            case 'template':
+                $sResult = $this->_sCacheFilePrefix;
+                break;
+
+            case 'less':
+                $sResult = $this->_sCssLessPrefix;
+                break;
+
+            case 'css':
+                $sResult = $this->_sCssCachePrefix;
+                break;
+
+            case 'js':
+                $sResult = $this->_sJsCachePrefix;
+                break;
+    	}
+
+    	return $sResult;
+    }
+    /**
+     * Get cache object for templates
+     * @return cache class instance
+     */
+    function getTemplatesCacheObject ()
+    {
+        $sCacheEngine = getParam('sys_template_cache_engine');
+        $oCacheEngine = bx_instance('BxDolCache' . $sCacheEngine);
+        if(!$oCacheEngine->isAvailable())
+            $oCacheEngine = bx_instance('BxDolCacheFileHtml');
+        return $oCacheEngine;
+    }
+    /**
+     * Get template from cache if it's enabled.
+     *
+     * @param  string  $sName               template name
+     * @param  string  $aVariables          key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed   $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @param  string  $sCheckIn            where the content would be searched(base, template, both)
+     * @param  boolean $bEvaluate           need to evaluate the template or not.
+     * @return string  result of operation or false on failure.
+     */
+    function getCached($sName, &$aVariables, $mixedKeyWrapperHtml = null, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH, $bEvaluate = true)
+    {
+        // initialization
+
+        if(!$this->_bCacheEnable)
+            return false;
+
+        $sAbsolutePath = $this->_getAbsoluteLocation('path', $this->_sFolderHtml, $sName, $sCheckIn);
+        if(empty($sAbsolutePath))
+            return false;
+
+        $oCacheEngine = $this->getTemplatesCacheObject ();
+        $isFileBasedEngine = $bEvaluate && method_exists($oCacheEngine, 'getDataFilePath');
+
+        // try to get cached content
+
+        $sCacheVariableName = "a";
+        $sCacheKey = $this->_getCacheFileName('html', $sAbsolutePath) . '.php';
+        if ($isFileBasedEngine)
+            $sCacheContent = $oCacheEngine->getDataFilePath($sCacheKey);
+        else
+            $sCacheContent = $oCacheEngine->getData($sCacheKey);
+
+
+        // recreate cache if it is empty
+
+        if ($sCacheContent === null && ($sContent = file_get_contents($sAbsolutePath)) !== false && ($sContent = $this->_compileContent($sContent, "\$" . $sCacheVariableName, 1, $aVariables, $mixedKeyWrapperHtml)) !== false) {
+            if (false === $oCacheEngine->setData($sCacheKey, trim($sContent)))
+                return false;
+
+            if ($isFileBasedEngine)
+                $sCacheContent = $oCacheEngine->getDataFilePath($sCacheKey);
+            else
+                $sCacheContent = $sContent;
+        }
+
+        if ($sCacheContent === null)
+            return false;
+
+        // return simple cache content
+
+        if(!$bEvaluate)
+            return $sCacheContent;
+
+        // return evaluated cache content
+
+        ob_start();
+
+        $$sCacheVariableName = &$aVariables;
+
+        if ($isFileBasedEngine)
+            include($sCacheContent);
+        else
+            eval('?'.'>' . $sCacheContent);
+
+        $sContent = ob_get_clean();
+
+        return $sContent;
+    }
+
+    /**
+     * Add JS file(s) to global output.
+     *
+     * @param  mixed          $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @param  boolean        $bDynamic   in the dynamic mode JS file(s) are not included to global output, but are returned from the function directly.
+     * @return boolean/string result of operation.
+     */
+    function addJs($mixedFiles, $bDynamic = false)
+    {
+        return $this->_processFiles('js', 'add', $mixedFiles, $bDynamic);
+    }
+
+    /**
+     * get added js files
+     */ 
+    function getJs()
+    {
+        return $this->aPage['js_compiled'];
+    }
+    
+    /**
+     * Add System JS file(s) to global output.
+     * System JS files are the files which are attached to all pages. They will be cached separately from the others.
+     *
+     * @param  mixed          $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @param  boolean        $bDynamic   in the dynamic mode JS file(s) are not included to global output, but are returned from the function directly.
+     * @return boolean/string result of operation.
+     */
+    function addJsSystem($mixedFiles)
+    {
+        return $this->_processFiles('js', 'add', $mixedFiles, false, true);
+    }
+
+    /**
+     * Delete JS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @return boolean result of operation.
+     */
+    function deleteJs($mixedFiles)
+    {
+        return $this->_processFiles('js', 'delete', $mixedFiles);
+    }
+
+    /**
+     * Delete System JS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single JS file name. An array - array of JS file names.
+     * @return boolean result of operation.
+     */
+    function deleteJsSystem($mixedFiles)
+    {
+        return $this->_processFiles('js', 'delete', $mixedFiles, false, true);
+    }
+
+    /**
+     * Compile JS files in one file.
+     *
+     * @param  string $sAbsolutePath CSS file absolute path(full URL for external CSS/JS files).
+     * @param  array  $aIncluded     an array of already included JS files.
+     * @return string result of operation.
+     */
+    function _compileJs($sAbsolutePath, &$aIncluded)
+    {
+        if(isset($aIncluded[$sAbsolutePath]))
+           return '';
+
+        $bExternal = strpos($sAbsolutePath, "http://") !== false || strpos($sAbsolutePath, "https://") !== false;
+        if($bExternal) {
+            $sPath = $sAbsolutePath;
+            $sName = '';
+
+            $sContent = bx_file_get_contents($sAbsolutePath);
+        } else {
+            $aFileInfo = pathinfo($sAbsolutePath);
+            $sPath = $aFileInfo['dirname'] . DIRECTORY_SEPARATOR;
+            $sName = $aFileInfo['basename'];
+
+            $sContent = file_get_contents($sPath . $sName);
+        }
+
+        if(empty($sContent))
+            return '';
+
+        $sUrl = bx_ltrim_str($sPath, realpath(BX_DIRECTORY_PATH_ROOT), BX_DOL_URL_ROOT);
+        $sUrl = str_replace(DIRECTORY_SEPARATOR, '/', $sUrl);
+
+        $sContent = "\r\n/*--- BEGIN: " . $sUrl . $sName . "---*/\r\n" . $sContent . ";\r\n/*--- END: " . $sUrl . $sName . "---*/\r\n";
+        $sContent = str_replace(array("\n\r", "\r\n", "\r"), "\n", $sContent);
+
+        $aIncluded[$sAbsolutePath] = 1;
+
+        return preg_replace(
+            array(
+                "'<bx_url_root />'",
+                "'\r\n'"
+            ),
+            array(
+                BX_DOL_URL_ROOT,
+                "\n"
+            ),
+            $sContent
+        );
+    }
+
+	/**
+     * Minify JS
+     *
+     * @param  string $s JS string to minify
+     * @return string minified JS string.
+     */
+	function _minifyJs($s)
+    {
+        // since each JS file is minified separately, it has to be in own scope
+    	return "\n {\n" . BxDolMinify::getInstance()->minifyJs($s) . "\n }\n";
+    }
+
+    /**
+     * Wrap an URL to JS file into JS tag.
+     *
+     * @param  string $sFile - URL to JS file.
+     * @return string the result of operation.
+     */
+    function _wrapInTagJs($sFile)
+    {
+        return "<script language=\"javascript\" type=\"text/javascript\" src=\"" . $sFile . "\"></script>";
+    }
+
+    /**
+     * Wrap JS code into JS tag.
+     *
+     * @param  string $sCode - JS code.
+     * @return string the result of operation.
+     */
+    function _wrapInTagJsCode($sCode)
+    {
+        return "<script language=\"javascript\" type=\"text/javascript\">\n<!--\n" . $sCode . "\n-->\n</script>";
+    }
+
+    /**
+     * Add CSS file(s) to global output.
+     *
+     * @param  mixed          $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @param  boolean        $bDynamic   in the dynamic mode CSS file(s) are not included to global output, but are returned from the function directly.
+     * @return boolean/string result of operation
+     */
+    function addCss($mixedFiles, $bDynamic = false)
+    {
+        return $this->_processFiles('css', 'add', $mixedFiles, $bDynamic);
+    }
+
+    /**
+     * get added css files
+     */ 
+    function getCss()
+    {
+        return $this->aPage['css_compiled'];
+    }
+
+    /**
+     * Add additional heavy css file (not very necessary) to load asynchronously for desktop browsers only
+     * @param  mixed          $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     */
+    function addCssAsync($mixedFiles)
+    {
+        if (!is_array($mixedFiles))
+            $mixedFiles = array($mixedFiles);
+
+        foreach ($mixedFiles as $sFile)
+            $this->aPage['css_async'][] = $this->_getAbsoluteLocationCss('url', $sFile);
+
+        $this->addJs('loadCSS.js');
+    }
+
+    /**
+     * Return script tag with special code to load async css.
+     * This tag is added after js files list
+     */
+    function includeCssAsync ()
+    {
+        if (empty($this->aPage['css_async']))
+            return '';
+
+        $this->aPage['css_async'] = array_unique($this->aPage['css_async']);
+
+        $sList = '';
+        foreach ($this->aPage['css_async'] as $sUrl)
+            $sList .= 'loadCSS("' . $sUrl . '", document.getElementById("bx_css_async"));';
+
+        // don't load css for mobile devices
+        return '
+            <script id="bx_css_async">
+                if(!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
+                    ' . $sList . '
+                }
+            </script>
+        ';
+    }
+
+    /**
+     * Add System CSS file(s) to global output.
+     * System CSS files are the files which are attached to all pages. They will be cached separately from the others.
+     *
+     * @param  mixed          $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean/string result of operation
+     */
+    function addCssSystem($mixedFiles)
+    {
+        return $this->_processFiles('css', 'add', $mixedFiles, false, true);
+    }
+
+    /**
+     * Delete CSS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean result of operation.
+     */
+    function deleteCss($mixedFiles)
+    {
+        return $this->_processFiles('css', 'delete', $mixedFiles);
+    }
+    /**
+     * Delete System CSS file(s) from global output.
+     *
+     * @param  mixed   $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean result of operation.
+     */
+    function deleteCssSystem($mixedFiles)
+    {
+        return $this->_processFiles('css', 'delete', $mixedFiles, false, true);
+    }
+    /**
+     * Compile CSS files' structure(@see \@import css_file_path) in one file.
+     *
+     * @param  string $sAbsolutePath CSS file absolute path(full URL for external CSS/JS files).
+     * @param  array  $aIncluded     an array of already included CSS files.
+     * @return string result of operation.
+     */
+    function _compileCss($sAbsolutePath, &$aIncluded)
+    {
+        if(isset($aIncluded[$sAbsolutePath]))
+           return '';
+
+        $bExternal = strpos($sAbsolutePath, "http://") !== false || strpos($sAbsolutePath, "https://") !== false;
+        if($bExternal) {
+        	$sPath = $sAbsolutePath;
+            $sName = '';
+
+        	$aAPUrl = parse_url($sAbsolutePath);
+        	if(!empty($aAPUrl['path'])) {
+        		$aAPPath = pathinfo($aAPUrl['path']);
+        		if(!empty($aAPPath['basename'])) {
+        			$sPath = bx_rtrim_str($sAbsolutePath, $aAPPath['basename']);
+        			$sName = $aAPPath['basename'];
+        		}
+        	}
+
+            $sContent = bx_file_get_contents($sAbsolutePath);
+        } else {
+            $aFileInfo = pathinfo($sAbsolutePath);
+            $sPath = $aFileInfo['dirname'] . DIRECTORY_SEPARATOR;
+            $sName = $aFileInfo['basename'];
+
+            $sContent = file_get_contents($sPath . $sName);
+        }
+
+        if(empty($sContent))
+            return '';
+
+        $sUrl = bx_ltrim_str($sPath, realpath(BX_DIRECTORY_PATH_ROOT), BX_DOL_URL_ROOT);
+        $sUrl = str_replace(DIRECTORY_SEPARATOR, '/', $sUrl);
+
+        $sContent = "\r\n/*--- BEGIN: " . $sUrl . $sName . "---*/\r\n" . $sContent . "\r\n/*--- END: " . $sUrl . $sName . "---*/\r\n";
+        $aIncluded[$sAbsolutePath] = 1;
+
+        $sContent = str_replace(array("\n\r", "\r\n", "\r"), "\n", $sContent);
+        if($bExternal) {
+            $sContent = preg_replace(
+                array(
+                    "'@import\s+url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/_-]+)\s*[\'|\"]*\s*\)\s*;'",
+                    "'url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/\?\#_=-]+)\s*[\'|\"]*\s*\)'"
+                ),
+                array(
+                    "",
+                    "url(" . $sPath . "\\1)"
+                ),
+                $sContent
+            );
+        }
+        else {
+        	try {
+        		$oTemplate = &$this;
+
+        		/* Match URL based imports like the following:
+        		 * @import 'http://[domain]/modules/[vendor]/[module]/template/css/view.css';
+        		 * Is mainly needed for CSS files which are gotten from LESS compiler.
+        		 */
+        		$sContent = preg_replace_callback(
+	                "'@import\s+[\'|\"]*\s*" . str_replace("/", "\/", BX_DOL_URL_ROOT) . "([a-zA-Z0-9\.\/_-]+)\s*[\'|\"]*\s*;'", function ($aMatches)  use($oTemplate, $sPath, $aIncluded) {
+	                	return $oTemplate->_compileCss(realpath(BX_DIRECTORY_PATH_ROOT . $aMatches[1]), $aIncluded);
+	                }, $sContent);
+
+				/* Match relative path based imports like the following:
+				 * @import url(../../../../../../base/profile/template/css/main.css);
+				 * Is mainly needed for default CSS files.
+				 */
+	            $sContent = preg_replace_callback(
+	                "'@import\s+url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/_-]+)\s*[\'|\"]*\s*\)\s*;'", function ($aMatches)  use($oTemplate, $sPath, $aIncluded) {
+	                	return $oTemplate->_compileCss(realpath($sPath . dirname($aMatches[1])) . DIRECTORY_SEPARATOR . basename($aMatches[1]), $aIncluded);
+	                }, $sContent);
+
+	            $sContent = preg_replace_callback(
+	                "'url\s*\(\s*[\'|\"]*\s*([a-zA-Z0-9\.\/\?\#_=-]+)\s*[\'|\"]*\s*\)'", function ($aMatches)  use($oTemplate, $sPath) {
+						$sFile = basename($aMatches[1]);
+				        $sDirectory = dirname($aMatches[1]);
+
+				        $sRootPath = realpath(BX_DIRECTORY_PATH_ROOT);
+				        $sAbsolutePath = realpath(addslashes($sPath) . $sDirectory) . DIRECTORY_SEPARATOR . $sFile;
+
+				        $sRootPath = str_replace(DIRECTORY_SEPARATOR, '/', $sRootPath);
+				        $sAbsolutePath = str_replace(DIRECTORY_SEPARATOR, '/', $sAbsolutePath);
+
+				        return 'url(' . bx_ltrim_str($sAbsolutePath, $sRootPath, BX_DOL_URL_ROOT) . ')';
+	                }, $sContent);
+			}
+	    	catch(Exception $oException) {
+	        	return '';
+	        }
+        }
+
+        return $sContent;
+    }
+
+    /**
+     * Less CSS
+     *
+     * @param  mixed $mixed CSS string to process with Less compiler or an array with CSS file's Path and URL.
+     * @return mixed string or an array with CSS file's Path and URL.
+     */
+    function _lessCss($mixed)
+    {
+    	require_once(BX_DIRECTORY_PATH_PLUGINS . 'lessphp/Less.php');
+
+        if(is_array($mixed) && isset($mixed['url']) && isset($mixed['path'])) {
+            $sPathFile = realpath($mixed['path']);
+            $aInfoFile = pathinfo($sPathFile);
+            if (!isset($aInfoFile['extension']) || $aInfoFile['extension'] != 'less')
+                return $mixed;
+
+            require_once(BX_DIRECTORY_PATH_PLUGINS . 'lessphp/Cache.php');
+        	$aFiles = array($mixed['path'] => $mixed['url']);
+        	$aOptions = array('cache_dir' => $this->_sCachePublicFolderPath, 'prefix' => $this->_sCssLessPrefix);
+        	$sFile = Less_Cache::Get($aFiles, $aOptions, $this->_oTemplateConfig->aLessConfig);
+
+            return array('url' => $this->_sCachePublicFolderUrl . $sFile, 'path' => $this->_sCachePublicFolderPath . $sFile);
+        }
+
+        $oLess = new Less_Parser();
+        $oLess->ModifyVars($this->_oTemplateConfig->aLessConfig);
+        $oLess->parse($mixed);
+        return $oLess->getCss();
+    }
+
+    /**
+     * Minify CSS
+     *
+     * @param  string $s CSS string to minify
+     * @return string minified CSS string.
+     */
+    function _minifyCss($s)
+    {
+    	return BxDolMinify::getInstance()->minifyCss($s);
+    }
+
+    /**
+     * Wrap an URL to CSS file into CSS tag.
+     *
+     * @param  string $sFile - URL to CSS file.
+     * @return string the result of operation.
+     */
+    function _wrapInTagCss($sFile)
+    {
+        if (!$sFile)
+            return '';
+        return "<link href=\"" . $sFile . "\" rel=\"stylesheet\" type=\"text/css\" />";
+    }
+    /**
+     * Wrap CSS code into CSS tag.
+     *
+     * @param  string $sCode - CSS code.
+     * @return string the result of operation.
+     */
+    function _wrapInTagCssCode($sCode)
+    {
+        return "<style>" . $sCode . "</style>";
+    }
+	/**
+     *  Include CSS style(s) in the page's head section.
+     */
+	function includeCssStyles()
+	{
+		$sResult = "";
+		if(empty($this->aPage['css_styles']) || !is_array($this->aPage['css_styles']))
+			return $sResult;
+
+		foreach($this->aPage['css_styles'] as $sName => $aContent) {
+			$sContent = "";
+			if(!empty($aContent) && is_array($aContent))
+				foreach($aContent as $sStyleName => $sStyleValue)
+					$sContent .= "\t" . $sStyleName . ": " . $sStyleValue . ";\r\n";
+
+			$sResult .= $sName . " {\r\n" . $sContent . "}\r\n";
+		}
+
+		return !empty($sResult) ? $this->_wrapInTagCssCode($sResult) : '';
+	}
+    /**
+     * Include CSS/JS file(s) attached to the page in its head section.
+     * @see the method is system and would be called automatically.
+     *
+     * @param  string $sType the type of file('js' or 'css')
+     * @return string the result CSS code.
+     */
+    function includeFiles($sType, $bSystem = false)
+    {
+        $sUpcaseType = ucfirst($sType);
+
+        $sArrayKey = $sType . ($bSystem ? '_system' : '_compiled');
+        $aFiles = isset($this->aPage[$sArrayKey]) ? $this->aPage[$sArrayKey] : array();
+        if(empty($aFiles) || !is_array($aFiles))
+            return "";
+
+        if(!$this->{'_b' . $sUpcaseType . 'Cache'})
+            return $this->_includeFiles($sType, $aFiles);
+
+        //--- If cache already exists, return it ---//
+        $sMethodWrap = '_wrapInTag' . $sUpcaseType;
+        $sMethodCompile = '_compile' . $sUpcaseType;
+        $sMethodLess = '_less' . $sUpcaseType;
+        $sMethodMinify = '_minify' . $sUpcaseType;
+
+        ksort($aFiles);
+
+        $sName = "";
+        foreach($aFiles as $aFile)
+            $sName .= $aFile['url'];
+        $sName = $this->_getCacheFileName($sType, $sName);
+
+        $sCacheAbsoluteUrl = $this->_sCachePublicFolderUrl . $sName . '.' . $sType;
+        $sCacheAbsolutePath = $this->_sCachePublicFolderPath . $sName . '.' . $sType;
+        if(file_exists($sCacheAbsolutePath)) {
+            if($this->{'_b' . $sUpcaseType . 'Archive'})
+                $sCacheAbsoluteUrl = $this->_getLoaderUrl($sType, $sName);
+
+           return $this->$sMethodWrap($sCacheAbsoluteUrl);
+        }
+
+        //--- Collect all attached CSS/JS in one file ---//
+
+        $sResult = "";
+        $aIncluded = array();
+        foreach($aFiles as $aFile) {
+            if($this->{'_b' . $sUpcaseType . 'Less'})
+                $aFile = $this->$sMethodLess($aFile);
+
+            if(($sContent = $this->$sMethodCompile($aFile['path'], $aIncluded)) === false)
+                continue;                
+
+            if(!preg_match('/[\.-]min.(js|css)$/i', $aFile['path']) && $this->{'_b' . $sUpcaseType . 'Minify'}) // don't minify minified files
+                $sContent = $this->$sMethodMinify($sContent);
+            
+            $sResult .= $sContent;
+        }
+
+        $mixedWriteResult = false;
+        if(!empty($sResult) && ($rHandler = fopen($sCacheAbsolutePath, 'w')) !== false) {
+            $mixedWriteResult = fwrite($rHandler, $sResult);
+            fclose($rHandler);
+            @chmod ($sCacheAbsolutePath, BX_DOL_FILE_RIGHTS);
+        }
+
+        if($mixedWriteResult === false)
+            return $this->_includeFiles($sType, $aFiles);
+
+        if($this->{'_b' . $sUpcaseType . 'Archive'})
+            $sCacheAbsoluteUrl = $this->_getLoaderUrl($sType, $sName);
+
+        return $this->$sMethodWrap($sCacheAbsoluteUrl);
+    }
+    /**
+     * Include CSS/JS files without caching.
+     *
+     * @param  string $sType  the file type (css or js)
+     * @param  array  $aFiles CSS/JS files to be added to the page.
+     * @return string result of operation.
+     */
+    function _includeFiles($sType, &$aFiles)
+    {
+        $iRev = (int)getParam('sys_revision');
+        $sUpcaseType = ucfirst($sType);
+
+        $sMethodWrap = '_wrapInTag' . $sUpcaseType;
+        $sMethodLess = '_less' . $sUpcaseType;
+
+        $sResult = "";
+        foreach($aFiles as $aFile) {
+            if($this->{'_b' . $sUpcaseType . 'Less'})
+                $aFile = $this->$sMethodLess($aFile);
+
+            $sFileUrl = $aFile['url'];
+            if(!$this->{'_b' . $sUpcaseType . 'Cache'})
+                $sFileUrl = bx_append_url_params($sFileUrl, array(
+                    'rev' => $iRev
+                ));
+
+            $sResult .= $this->$sMethodWrap($sFileUrl);
+        }
+
+        return $sResult;
+    }
+    /**
+     * Insert/Delete CSS file from output stack.
+     *
+     * @param  string  $sType      the file type (css or js)
+     * @param  string  $sAction    add/delete
+     * @param  mixed   $mixedFiles string value represents a single CSS file name. An array - array of CSS file names.
+     * @return boolean result of operation.
+     */
+    function _processFiles($sType, $sAction, $mixedFiles, $bDynamic = false, $bSystem = false)
+    {
+        if(empty($mixedFiles))
+            return $bDynamic ? "" : false;
+
+        if(is_string($mixedFiles))
+            $mixedFiles = array($mixedFiles);
+
+        $iRev = (int)getParam('sys_revision');
+
+        $sUpcaseType = ucfirst($sType);
+        $sMethodLocate = '_getAbsoluteLocation' . $sUpcaseType;
+        $sMethodWrap = '_wrapInTag' . $sUpcaseType;
+        $sResult = '';
+        foreach($mixedFiles as $sFile) {
+            //--- Process 3d Party CSS/JS file ---//
+            if(strpos($sFile, "http://") !== false || strpos($sFile, "https://") !== false) {
+                $sUrl = $sFile;
+                $sPath = $sFile;
+            }
+            //--- Process Custom CSS/JS file ---//
+            else if(strpos($sFile, "|") !== false) {
+                $sFile = implode('', explode("|", $sFile));
+                $sFile = bx_ltrim_str($sFile, BX_DIRECTORY_PATH_ROOT);
+
+                $sUrl = BX_DOL_URL_ROOT . $sFile;
+                $sPath = realpath(BX_DIRECTORY_PATH_ROOT . $sFile);
+            }
+            //--- Process Common CSS/JS file(check in default locations) ---//
+            else {
+                $sUrl = $this->$sMethodLocate('url', $sFile);
+                $sPath = $this->$sMethodLocate('path', $sFile);
+            }
+
+            if(empty($sPath) || empty($sUrl))
+                continue;
+
+            $sArrayKey = $sType . ($bSystem ? '_system' : '_compiled');
+            switch($sAction) {
+                case 'add':
+                    if($bDynamic)
+                        $sResult .= $this->$sMethodWrap(bx_append_url_params($sUrl, array(
+                            'rev' => $iRev
+                        )));
+                    else {
+                        $bFound = false;
+                        $aSearchIn = $bSystem ? $this->aPage[$sArrayKey] : array_merge($this->aPage[$sType . '_system'], $this->aPage[$sArrayKey]);
+                        foreach($aSearchIn as $iKey => $aValue)
+                            if($aValue['url'] == $sUrl && $aValue['path'] == $sPath) {
+                                $bFound = true;
+                                break;
+                            }
+
+                        if(!$bFound)
+                            $this->aPage[$sArrayKey][] = array('url' => $sUrl, 'path' => $sPath);
+                    }
+                    break;
+                case 'delete':
+                    if(!$bDynamic)
+                        foreach($this->aPage[$sArrayKey]  as $iKey => $aValue)
+                            if($aValue['url'] == $sUrl) {
+                                unset($this->aPage[$sArrayKey][$iKey]);
+                                break;
+                            }
+                    break;
+            }
+        }
+
+        return $bDynamic ? $sResult : true;
+    }
+
+    /**
+     * Parse content.
+     *
+     * @param  string $sContent            - HTML file's content.
+     * @param  array  $aVariables          - key/value pairs. key should be the same as template's key, but without prefix and postfix.
+     * @param  mixed  $mixedKeyWrapperHtml - key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @return string the result of operation.
+     */
+    function _parseContent($sContent, $aVariables, $mixedKeyWrapperHtml = null)
+    {
+        $aKeysSrc = array_keys($aVariables);
+        $aValuesSrc = array_values($aVariables);
+
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        $sKeyIf = 'bx_if:';
+        $sKeyRepeat = 'bx_repeat:';
+        $iCountKeys = count($aKeysSrc);
+        $aKeys = $aValues = array();
+
+        //--- Parse simple keys ---//
+        for ($i = 0; $i < $iCountKeys; $i++) {
+            if (strncmp($aKeysSrc[$i], $sKeyRepeat, 10) === 0 || strncmp($aKeysSrc[$i], $sKeyIf, 6) === 0)
+                continue;
+
+            $aKeys[] = "'" . $aKeyWrappers['left'] . $aKeysSrc[$i] . $aKeyWrappers['right'] . "'s";
+            $aValues[] = str_replace('$', '\\$', $aValuesSrc[$i]);
+        }
+
+        //--- Parse keys with constructions ---//
+        for ($i = 0; $i < $iCountKeys; $i++) {
+            if (strncmp($aKeysSrc[$i], $sKeyRepeat, 10) === 0) {
+                $sKey = "'<" . $aKeysSrc[$i] . ">(.*)<\/" . $aKeysSrc[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1])) {
+                    if(is_array($aValuesSrc[$i]))
+                        foreach($aValuesSrc[$i] as $aValue)
+                            $sValue .= $this->parseHtmlByContent($aMatches[1], $aValue, $mixedKeyWrapperHtml);
+                    else if(is_string($aValuesSrc[$i]))
+                        $sValue = $aValuesSrc[$i];
+                }
+            } 
+            else if (strncmp($aKeysSrc[$i], $sKeyIf, 6) === 0) {
+                $sKey = "'<" . $aKeysSrc[$i] . ">(.*)<\/" . $aKeysSrc[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1]))
+                    if(is_array($aValuesSrc[$i]) && isset($aValuesSrc[$i]['content']) && isset($aValuesSrc[$i]['condition']) && $aValuesSrc[$i]['condition'])
+                        $sValue .= $this->parseHtmlByContent($aMatches[1], $aValuesSrc[$i]['content'], $mixedKeyWrapperHtml);
+            } 
+            else 
+                continue;
+
+            $aKeys[] = $sKey;
+            $aValues[] = str_replace('$', '\\$', $sValue);
+        }
+
+        try {
+        	$oTemplate = &$this;
+
+        	$aCallbackPatterns = array(
+        		"'<bx_include_auto:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_BOTH,
+        		"'<bx_include_base:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_BASE,
+        		"'<bx_include_tmpl:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_TMPL
+        	);
+	        foreach($aCallbackPatterns as $sPattern => $sCheckIn)
+		        $sContent = preg_replace_callback($sPattern, function($aMatches) use($oTemplate, $aVariables, $mixedKeyWrapperHtml, $sCheckIn) {
+		        	return $oTemplate->parseHtmlByName($aMatches[1], $aVariables, $mixedKeyWrapperHtml, $sCheckIn);
+		        }, $sContent);
+
+			$sContent = $this->_parseContentKeys($sContent, array(
+				"'<bx_injection:([^\s]+) />'s" => 'get_injection',
+            	"'<bx_menu:([^\s]+) \/>'s" => 'get_menu',
+			));
+        }
+		catch(Exception $oException) {
+        	return '';
+        }
+
+        $aKeys = array_merge($aKeys, array(
+            "'<bx_url_root />'",
+            "'<bx_url_studio />'",
+        ));
+        $aValues = array_merge($aValues, array(
+            BX_DOL_URL_ROOT,
+            BX_DOL_URL_STUDIO,
+        ));
+
+        //--- Parse Predefined Keys ---//
+        $sContent = preg_replace($aKeys, $aValues, $sContent);
+
+        //--- Parse System Keys ---//
+        $sContent = preg_replace_callback("'" . $aKeyWrappers['left'] . "([a-zA-Z0-9_-]+)" . $aKeyWrappers['right'] . "'", function($aMatches) use($oTemplate, $mixedKeyWrapperHtml) {
+        	return $oTemplate->parseSystemKey($aMatches[1], $mixedKeyWrapperHtml);
+        }, $sContent);
+
+        return $sContent;
+    }
+
+    /**
+     * Compile content
+     *
+     * @param  string  $sContent            template.
+     * @param  string  $aVarName            variable name to be saved in the output file.
+     * @param  integer $iVarDepth           depth is used to process nesting, for example, in cycles.
+     * @param  array   $aVarValues          values to be compiled in.
+     * @param  mixed   $mixedKeyWrapperHtml key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @return string  the result of operation.
+     */
+    function _compileContent($sContent, $aVarName, $iVarDepth, $aVarValues, $mixedKeyWrapperHtml = null)
+    {
+        $aKeys = array_keys($aVarValues);
+        $aValues = array_values($aVarValues);
+
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        for($i = 0; $i < count($aKeys); $i++) {
+            if(strpos($aKeys[$i], 'bx_repeat:') === 0) {
+                $sKey = "'<" . $aKeys[$i] . ">(.*)<\/" . $aKeys[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1])) {
+                    if(empty($aValues[$i]) || !is_array($aValues[$i]))
+                        return false;
+
+                    $sIndex = "\$" . str_repeat("i", $iVarDepth);
+                    $sValue .= "<?php if(is_array(" . $aVarName . "['" . $aKeys[$i] . "'])) for(" . $sIndex . "=0; " . $sIndex . "<count(" . $aVarName . "['" . $aKeys[$i] . "']); " . $sIndex . "++){ ?>";
+                    if(($sInnerValue = $this->_compileContent($aMatches[1], $aVarName . "['" . $aKeys[$i] . "'][" . $sIndex . "]", $iVarDepth + 1, current($aValues[$i]), $mixedKeyWrapperHtml)) === false)
+                        return false;
+                    $sValue .= $sInnerValue;
+                    $sValue .= "<?php } else if(is_string(" . $aVarName . "['" . $aKeys[$i] . "'])) echo " . $aVarName . "['" . $aKeys[$i] . "']; ?>";
+                }
+            } else if(strpos($aKeys[$i], 'bx_if:') === 0) {
+                $sKey = "'<" . $aKeys[$i] . ">(.*)<\/" . $aKeys[$i] . ">'s";
+
+                $aMatches = array();
+                preg_match($sKey, $sContent, $aMatches);
+
+                $sValue = '';
+                if(isset($aMatches[1]) && !empty($aMatches[1])) {
+                    if(!is_array($aValues[$i]) || !isset($aValues[$i]['content']) || empty($aValues[$i]['content']) || !is_array($aValues[$i]['content']))
+                        return false;
+
+                    $sValue .= "<?php if(" . $aVarName . "['" . $aKeys[$i] . "']['condition']){ ?>";
+                    if(($sInnerValue = $this->_compileContent($aMatches[1], $aVarName . "['" . $aKeys[$i] . "']['content']", $iVarDepth, $aValues[$i]['content'], $mixedKeyWrapperHtml)) === false)
+                        return false;
+                    $sValue .= $sInnerValue;
+                    $sValue .= "<?php } ?>";
+                }
+            } else {
+                $sKey = "'" . $aKeyWrappers['left'] . $aKeys[$i] . $aKeyWrappers['right'] . "'s";
+                $sValue = "<?php echo " . $aVarName . "['" . $aKeys[$i] . "'];?>";
+            }
+
+            $aKeys[$i] = $sKey;
+            $aValues[$i] = $sValue;
+        }
+
+        try {
+        	$oTemplate = &$this;
+
+        	$aCallbackPatterns = array(
+        		"'<bx_include_auto:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_BOTH,
+        		"'<bx_include_base:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_BASE,
+        		"'<bx_include_tmpl:([^\s]+) \/>'s" => BX_DOL_TEMPLATE_CHECK_IN_TMPL
+        	);
+	        foreach($aCallbackPatterns as $sPattern => $sCheckIn)
+		        $sContent = preg_replace_callback($sPattern, function($aMatches) use($oTemplate, $aVarValues, $mixedKeyWrapperHtml, $sCheckIn) {
+		        	$mixedResult = $oTemplate->getCached($aMatches[1], $aVarValues, $mixedKeyWrapperHtml, $sCheckIn, false);
+		        	if($mixedResult === false)
+		        		throw new Exception('Unable to create cache file.');
+
+		        	return $mixedResult;
+		        }, $sContent);
+
+			$sContent = $this->_parseContentKeys($sContent);
+        }
+        catch(Exception $oException) {
+        	return false;
+        }
+
+        $aKeys = array_merge($aKeys, array(
+            "'<bx_injection:([^\s]+) />'s",
+            "'<bx_menu:([^\s]+) \/>'s",
+            "'<bx_url_root />'",
+            "'<bx_url_studio />'"
+        ));
+        $aValues = array_merge($aValues, array(
+            "<?php echo \$this->processInjection(\$this->aPage['name_index'], '\\1'); ?>",
+            "<?php echo \$this->getMenu('\\1'); ?>",
+            BX_DOL_URL_ROOT,
+            BX_DOL_URL_STUDIO
+        ));
+
+        //--- Parse Predefined Keys ---//
+        $sContent = preg_replace($aKeys, $aValues, $sContent);
+        //--- Parse System Keys ---//
+        $sContent = preg_replace( "'" . $aKeyWrappers['left'] . "([a-zA-Z0-9_-]+)" . $aKeyWrappers['right'] . "'", "<?php echo \$this->parseSystemKey('\\1', \$mixedKeyWrapperHtml);?>", $sContent);
+
+        return $sContent;
+    }
+    protected function _parseContentKeys($sContent, $aCallbackPatterns = array())
+    {
+    	$oTemplate = &$this;
+
+    	$aCallbackPatterns = array_merge($aCallbackPatterns, array(
+			"'<bx_image_url:([^\s]+) \/>'s" => "get_image_url",
+			"'<bx_icon_url:([^\s]+) \/>'s" => "get_icon_url",
+			"'<bx_text:([_\{\}\w\d\s]+[^\s]{1}) \/>'s" => "get_text",
+			"'<bx_text_js:([^\s]+) \/>'s" => "get_text_js",
+			"'<bx_text_attribute:([^\s]+) \/>'s" => "get_text_attribute",
+		));
+
+		foreach($aCallbackPatterns as $sPattern => $sAction)
+			$sContent = preg_replace_callback($sPattern, function($aMatches) use($oTemplate, $sAction) {
+		    	$sResult = '';
+
+	        	switch($sAction) {
+	        		case 'get_image_url':
+	        			$sResult = $oTemplate->getImageUrl($aMatches[1]);
+	        			break;
+	        		case 'get_icon_url':
+	        			$sResult = $oTemplate->getIconUrl($aMatches[1]);
+	        			break;
+					case 'get_text':
+	        			$sResult = _t($aMatches[1]);
+	        			break;
+	        		case 'get_text_js':
+	        			$sResult = bx_js_string(_t($aMatches[1]));
+	        			break;
+	        		case 'get_text_attribute':
+	        			$sResult = bx_html_attribute(_t($aMatches[1]));
+	        			break;
+	        		case 'get_injection':
+	        			$sResult = $oTemplate->processInjection($oTemplate->getPageNameIndex(), $aMatches[1]);
+	        			break;
+	        		case 'get_menu':
+	        			$sResult = $oTemplate->getMenu($aMatches[1]);
+	        			break;
+	        	}
+
+	        	return $sResult;
+	        }, $sContent);
+
+		return $sContent;
+    }
+
+    /**
+     * Get absolute location of some template's part.
+     *
+     * @param  string $sType    - result type. Available values 'url' and 'path'.
+     * @param  string $sFolder  - folders to be searched in. @see $_sFolderHtml, $_sFolderCss, $_sFolderImages and $_sFolderIcons
+     * @param  string $sName    - requested part name.
+     * @param  string $sCheckIn where the content would be searched(base, template, both)
+     * @return string absolute location (path/url) of the part.
+     */
+    function _getAbsoluteLocation($sType, $sFolder, $sName, $sCheckIn = BX_DOL_TEMPLATE_CHECK_IN_BOTH)
+    {
+        $sDirectory = $this->getPath();
+
+        if($sType == 'path') {
+            $sDivider = DIRECTORY_SEPARATOR;
+            $sRoot = BX_DIRECTORY_PATH_ROOT;
+        } else if($sType == 'url') {
+            $sDivider = '/';
+            $sRoot = BX_DOL_URL_ROOT;
+        }
+
+        if(strpos($sName,'|') !== false) {
+            $aParts = explode('|', $sName);
+            $sName = $aParts[1];
+
+            if(strpos($aParts[0],'@') !== false) {
+                $aLocationParts = explode('@', $aParts[0]);
+                $sLocationKey = $this->addLocation($aLocationParts[0], BX_DIRECTORY_PATH_ROOT . $aLocationParts[1], BX_DOL_URL_ROOT . $aLocationParts[1]);
+            }
+        }
+
+        $sResult = '';
+        $aLocations = array_reverse($this->_aLocations, true);
+        foreach($aLocations as $sKey => $aLocation) {
+            if(($sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_BOTH || $sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_TMPL) && extFileExists(BX_DIRECTORY_PATH_MODULES . $this->getPath(). 'data' . DIRECTORY_SEPARATOR . BX_DOL_TEMPLATE_FOLDER_ROOT . DIRECTORY_SEPARATOR . $sKey . DIRECTORY_SEPARATOR . $sFolder . $sName))
+                $sResult = $sRoot . 'modules' . $sDivider . $sDirectory. 'data' . $sDivider . BX_DOL_TEMPLATE_FOLDER_ROOT . $sDivider . $sKey . $sDivider . $sFolder . $sName;
+            else if(($sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_BOTH || $sCheckIn == BX_DOL_TEMPLATE_CHECK_IN_BASE) && extFileExists($aLocation['path'] . BX_DOL_TEMPLATE_FOLDER_ROOT . DIRECTORY_SEPARATOR . $sFolder . $sName))
+                $sResult = $aLocation[$sType] . BX_DOL_TEMPLATE_FOLDER_ROOT . $sDivider . $sFolder . $sName;
+            else
+                continue;
+            break;
+        }
+
+        /**
+         * try to find from received path
+         */
+        if(!$sResult && @is_file(BX_DIRECTORY_PATH_ROOT . $aParts[0] . DIRECTORY_SEPARATOR . $aParts[1])) {
+            $sResult = $sRoot . $aParts[0] . $sDivider . $aParts[1];
+        }
+
+        if(isset($sLocationKey))
+           $this->removeLocation($sLocationKey);
+
+        return $sType == 'path' && !empty($sResult) ? realpath($sResult) : $sResult;
+    }
+    /**
+     * Get absolute location of some template's part.
+     *
+     * @param  string $sType result type. Available values 'url' and 'path'.
+     * @param  string $sName requested part name.
+     * @return string absolute location (path/url) of the part.
+     */
+    function _getAbsoluteLocationJs($sType, $sName)
+    {
+        $sResult = '';
+        $aLocations = array_reverse($this->_aLocationsJs, true);
+        foreach($aLocations as $sKey => $aLocation) {
+            if(extFileExists($aLocation['path'] . $sName))
+                $sResult = $aLocation[$sType] . $sName;
+            else
+                continue;
+            break;
+        }
+        return $sType == 'path' && !empty($sResult) ? realpath($sResult) : $sResult;
+    }
+    function _getAbsoluteLocationCss($sType, $sName)
+    {
+    	$sNameLess = str_replace('.css', '.less', $sName);
+
+    	$sResult = $this->_getAbsoluteLocation($sType, $this->_sFolderCss, $sNameLess);
+    	if(!empty($sResult))
+    		return $sResult;
+
+        return $this->_getAbsoluteLocation($sType, $this->_sFolderCss, $sName);
+    }
+    /**
+     * Get inline data for Images and Icons.
+     *
+     * @param  string  $sType    image/icon
+     * @param  string  $sName    file name
+     * @param  string  $sCheckIn where the content would be searched(base, template, both)
+     * @return unknown
+     */
+    function _getInlineData($sType, $sName, $sCheckIn)
+    {
+        switch($sType) {
+            case 'image':
+                $sFolder = $this->_sFolderImages;
+                break;
+            case 'icon':
+                $sFolder = $this->_sFolderIcons;
+                break;
+        }
+        $sPath = $this->_getAbsoluteLocation('path', $sFolder, $sName, $sCheckIn);
+
+        $iFileSize = 0;
+        if($this->_bImagesInline && ($iFileSize = filesize($sPath)) !== false && $iFileSize < $this->_iImagesMaxSize) {
+            $aFileInfo = pathinfo($sPath);
+            return $this->getImageMimeType($aFileInfo['extension']) . ";base64," . base64_encode(file_get_contents($sPath));
+        }
+
+        return false;
+    }
+
+    /**
+     * Get file name where the template would be cached.
+     *
+     * @param  string $sAbsolutePath template's real path.
+     * @return string the result of operation.
+     */
+    function _getCacheFileName($sType, $sAbsolutePath)
+    {
+        $sResult = bx_site_hash($sAbsolutePath);
+        switch($sType) {
+            case 'html':
+                $sResult = $this->_sCacheFilePrefix . bx_lang_name() . '_' . $this->_sCode .  '_' . $sResult;
+                break;
+            case 'css':
+                $sResult = $this->_sCssCachePrefix . (!empty($this->_iMix) ? $this->_iMix . '_' : '') .  $sResult;
+                break;
+            case 'js':
+                $sResult = $this->_sJsCachePrefix . $sResult;
+                break;
+        }
+
+        return $sResult;
+    }
+    /**
+     * Get template key wrappers(left, right)
+     *
+     * @param  mixed $mixedKeyWrapperHtml key wrapper(string value if left and right parts are the same, array(0 => left, 1 => right) otherwise).
+     * @return array result of operation.
+     */
+    function _getKeyWrappers($mixedKeyWrapperHtml)
+    {
+        $aResult = array();
+        if(!empty($mixedKeyWrapperHtml) && is_string($mixedKeyWrapperHtml))
+            $aResult = array('left' => $mixedKeyWrapperHtml, 'right' => $mixedKeyWrapperHtml);
+        else if(!empty($mixedKeyWrapperHtml) && is_array($mixedKeyWrapperHtml))
+            $aResult = array('left' => $mixedKeyWrapperHtml[0], 'right' => $mixedKeyWrapperHtml[1]);
+        else
+            $aResult = array('left' => $this->_sKeyWrapperHtml, 'right' => $this->_sKeyWrapperHtml);
+        return $aResult;
+    }
+
+    /**
+     * Process all added language translations and return them as a string.
+     *
+     * @return string with JS code.
+     */
+    function _processJsTranslations()
+    {
+        $sReturn = '';
+        if(isset($this->aPage['js_translations']) && is_array($this->aPage['js_translations'])) {
+            foreach($this->aPage['js_translations'] as $sKey => $sString)
+                $sReturn .= "'" .  bx_js_string($sKey) . "': '" . bx_js_string($sString) . "',";
+
+            $sReturn = substr($sReturn, 0, -1);
+        }
+
+        return '<script type="text/javascript" language="javascript">var aDolLang = {' . $sReturn . '};</script>';
+    }
+    /**
+     * Process all added options and return them as a string.
+     *
+     * @return string with JS code.
+     */
+    function _processJsOptions()
+    {
+        $sReturn = '';
+        if(isset($this->aPage['js_options']) && is_array($this->aPage['js_options'])) {
+            foreach($this->aPage['js_options'] as $sName => $mixedValue)
+                $sReturn .= "'" .  bx_js_string($sName) . "': '" . bx_js_string($mixedValue) . "',";
+
+            $sReturn = substr($sReturn, 0, -1);
+        }
+
+        return '<script type="text/javascript" language="javascript">var aDolOptions = {' . $sReturn . '};</script>';
+    }
+    /**
+     * Process all added images and return them as a string.
+     *
+     * @return string with JS code.
+     */
+    function _processJsImages()
+    {
+        $sReturn = '';
+        if(isset($this->aPage['js_images']) && is_array($this->aPage['js_images'])) {
+            foreach($this->aPage['js_images'] as $sKey => $sUrl)
+                $sReturn .= "'" .  bx_js_string($sKey) . "': '" . bx_js_string($sUrl) . "',";
+
+            $sReturn = substr($sReturn, 0, -1);
+        }
+
+        return '<script type="text/javascript" language="javascript">var aDolImages = {' . $sReturn . '};</script>';
+    }
+
+    /**
+     * Get Gzip loader URL.
+     *
+     * @param $sType content type CSS/JS
+     * @param $sName file name.
+     * @return string with URL
+     */
+    function _getLoaderUrl($sType, $sName)
+    {
+        return BX_DOL_URL_ROOT . 'gzip_loader.php?file=' . $sName . '.' . $sType;
+    }
+
+    /**
+     *
+     * Functions to display pages with errors, messages and so on.
+     *
+     */
+    function displayAccessDenied ($sMsg = '', $iPage = BX_PAGE_DEFAULT, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        bx_import('BxDolLanguages');
+        header('HTTP/1.0 403 Forbidden');
+        header('Status: 403 Forbidden');
+        $this->displayMsg($sMsg ? $sMsg : _t('_Access denied'), false, $iPage, $iDesignBox);
+    }
+
+    function displayNoData ($sMsg = '', $iPage = BX_PAGE_DEFAULT, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        bx_import('BxDolLanguages');
+        header('HTTP/1.0 204 No Content');
+        header('Status: 204 No Content');
+        $this->displayMsg($sMsg ? $sMsg : _t('_Empty'), false, $iPage, $iDesignBox);
+    }
+
+    function displayErrorOccured ($sMsg = '', $iPage = BX_PAGE_DEFAULT, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        bx_import('BxDolLanguages');
+        header('HTTP/1.0 500 Internal Server Error');
+        header('Status: 500 Internal Server Error');
+        $this->displayMsg($sMsg ? $sMsg : _t('_error occured'), false, $iPage, $iDesignBox);
+    }
+
+    function displayPageNotFound ($sMsg = '', $iPage = BX_PAGE_DEFAULT, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        bx_import('BxDolLanguages');
+        header('HTTP/1.0 404 Not Found');
+        header('Status: 404 Not Found');
+        $this->displayMsg($sMsg ? $sMsg : _t('_sys_request_page_not_found_cpt'), false, $iPage, $iDesignBox);
+    }
+
+    function displayMsg ($s, $bTranslate = false, $iPage = BX_PAGE_DEFAULT, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        $sError = '_Error';
+        $bArray = is_array($s);
+
+        $sTitle = $bArray ? $s['title'] : ($bTranslate ? $sError : _t($sError));
+        $sContent = $bArray ? $s['content'] : $s;
+
+        if($bTranslate) {
+            $sTitle = _t($sTitle);
+            $sContent = _t($sContent);
+        }
+
+        $sContent = MsgBox($sContent);
+        if($iPage == BX_PAGE_DEFAULT)
+            $sContent = DesignBoxContent($sTitle, $sContent, $iDesignBox);
+
+        $oTemplate = BxDolTemplate::getInstance();
+        $oTemplate->setPageNameIndex ($iPage);
+        $oTemplate->setPageHeader ($sTitle);
+        $oTemplate->setPageContent ('page_main_code', $sContent);
+        $oTemplate->getPageCode();
+        exit;
+    }
+    
+    /**
+     * * * * Static methods for work with template injections * * *
+     *
+     * Static method is used to add/replace the content of some key in the template.
+     * It's usefull when you don't want to modify existing template but need to add some data to existing template key.
+     *
+     * @param  integer $iPageIndex - page index where injections would processed. Use 0 if you want it to be done on all the pages.
+     * @param  string  $sKey       - template key.
+     * @param  string  $sValue     - the data to be added.
+     * @return string  the result of operation.
+     */
+    function processInjection($iPageIndex, $sKey, $sValue = "")
+    {
+        if($iPageIndex != 0 && isset($this->aPage['injections']['page_0'][$sKey]) && isset($this->aPage['injections']['page_' . $iPageIndex][$sKey]))
+           $aSelection = @array_merge($this->aPage['injections']['page_0'][$sKey], $this->aPage['injections']['page_' . $iPageIndex][$sKey]);
+        else if(isset($this->aPage['injections']['page_0'][$sKey]))
+           $aSelection = $this->aPage['injections']['page_0'][$sKey];
+        else if(isset($this->aPage['injections']['page_' . $iPageIndex][$sKey]))
+            $aSelection = $this->aPage['injections']['page_' . $iPageIndex][$sKey];
+        else
+            $aSelection = array();
+
+        if(is_array($aSelection))
+            foreach($aSelection as $aInjection) {
+
+                if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginInjection($sRand = time().rand());
+
+                $sInjData = '';
+                switch($aInjection['type']) {
+                    case 'text':
+                        $sInjData = $aInjection['data'];
+                        break;
+
+                    case 'service':
+                    	if(BxDolService::isSerializedService($aInjection['data']))
+                    		$sInjData = BxDolService::callSerialized($aInjection['data']);                    	
+                        break;
+                }
+
+                if((int)$aInjection['replace'] == 1)
+                    $sValue = $sInjData;
+                else
+                    $sValue .= $sInjData;
+
+                if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endInjection($sRand, $aInjection);
+
+            }
+
+        return $sValue != '__' . $sKey . '__' ? str_replace('__' . $sKey . '__', '', $sValue) : $sValue;
+    }
+    /**
+     * Static method to add ingection available on the current page only.
+     *
+     * @param string  $sKey     - template's key.
+     * @param string  $sType    - injection type(text, php).
+     * @param string  $sData    - the data to be added.
+     * @param integer $iReplace - replace already existed data or not.
+     */
+    function addInjection($sKey, $sType, $sData, $iReplace = 0)
+    {
+        $this->aPage['injections']['page_0'][$sKey][] = array(
+            'page_index' => 0,
+            'key' => $sKey,
+            'type' => $sType,
+            'data' => $sData,
+            'replace' => $iReplace
+        );
+    }
+
+    function getPageCode($oTemplate = null)
+    {
+        if (empty($oTemplate))
+           $oTemplate = $this;
+
+        $oZ = new BxDolAlerts('system', 'design_before_output', 0, 0, array('page' => &$this->aPage, 'page_content' => &$this->aPageContent));
+        $oZ->alert();
+
+        header( 'Content-type: text/html; charset=utf-8' );
+        header( 'X-Frame-Options: sameorigin' );
+        echo $oTemplate->parsePageByName('page_' . $oTemplate->getPageNameIndex() . '.html', $oTemplate->getPageContent());
+    }
+}
+
+/** @} */

+ 249 - 0
upgrade/files/11.0.1-11.0.2/files/inc/js/classes/BxDolVoteReactions.js

@@ -0,0 +1,249 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+function BxDolVoteReactions(oOptions)
+{
+    BxDolVote.call(this, oOptions);
+
+    this._iTimeoutShowId = 0;
+    this._iTimeoutShowDelay = 750;
+    this._iTimeoutHideId = 0;
+    this._iTimeoutHideDelay = 1000;
+    this._fOnVoteIn = null;
+    this._fOnVoteOut = null;
+    this._fOnDoPopupIn = null;
+    this._fOnDoPopupOut = null;
+
+    this._sClassDo = 'bx-vote-do-vote';
+    this._sClassDoVoted = 'bx-vote-voted';
+
+    var $this = this;
+    $(document).ready(function() {
+        $this.initVote();
+    });
+}
+
+BxDolVoteReactions.prototype = Object.create(BxDolVote.prototype);
+BxDolVoteReactions.prototype.constructor = BxDolVoteReactions;
+
+BxDolVoteReactions.prototype.initVote = function()
+{
+    var $this = this;
+
+    this._fOnVoteIn = function() {
+        $this.onVoteIn(this);
+    };
+
+    this._fOnVoteOut = function() {
+        $this.onVoteOut(this);
+    };
+
+    $('#' + this._aHtmlIds['main'] + ' .' + this._sClassDo).hover(this._fOnVoteIn, this._fOnVoteOut);
+};
+
+BxDolVoteReactions.prototype.vote = function(oLink, iValue, sReaction, onComplete)
+{
+    var $this = this;
+    var oParams = this._getDefaultParams();
+    oParams['action'] = 'Vote';
+    oParams['value'] = iValue;
+    oParams['reaction'] = sReaction;
+
+    $.post(
+    	this._sActionsUrl,
+    	oParams,
+    	function(oData) {
+            if(oData && oData.message != undefined)
+                bx_alert(oData.message, function() {
+                    $this.onVote(oLink, oData, onComplete);
+                });
+            else
+                $this.onVote(oLink, oData, onComplete);
+        },
+        'json'
+    );
+};
+
+BxDolVoteReactions.prototype.onVote = function (oLink, oData, onComplete)
+{
+    var $this = this;
+
+    if(oData && oData.code != 0)
+        return;
+    
+    $('#' + this._aHtmlIds['do_popup']).dolPopupHide({});
+
+    oLink = $('.' + this._aHtmlIds['main'] + ' .' + this._sClassDo);
+
+    //--- Update Do button.
+    oLink.each(function() {
+        if(oData && oData.label_icon)
+            $(this).find('.sys-action-do-icon .sys-icon').attr('class', 'sys-icon ' + oData.label_icon);
+
+        if(oData && oData.label_title) {
+            $(this).attr('title', oData.label_title);
+            $(this).find('.sys-action-do-text').html(oData.label_title);
+        }
+
+        if(oData && oData.label_click)
+            $(this).attr('onclick', 'javascript:' + oData.label_click)
+
+        if(oData && oData.disabled)
+            $(this).removeAttr('onclick').addClass($(this).hasClass('bx-btn') ? 'bx-btn-disabled' : 'bx-vote-disabled');
+        else
+            $(this).toggleClass($this._sClassDoVoted);
+    });
+
+    //--- Update Counter.
+    var oCounter = this._getCounter(oLink);
+    if(oCounter && oCounter.length > 0) {
+        oCounter.filter('.' + oData.reaction).each(function() {
+            $(this).html(oData.countf).toggleClass('bx-vc-hidden', !oData.count);            
+        });
+
+        //--- Update Total.
+        if(oData.total)
+            oCounter.filter('.total-count').each(function() {
+                $(this).html(oData.total.countf).toggleClass('bx-vc-hidden', !oData.total.count);
+                $(this).parents('.' + $this._sSP + '-counter-wrapper:first').toggleClass('bx-vc-hidden', !oData.total.count);
+            });
+    }
+
+    if(typeof onComplete == 'function')
+        onComplete(oLink, oData);
+};
+
+BxDolVoteReactions.prototype.onVoteIn = function(oLink)
+{
+    if($(oLink).hasClass(this._sClassDoVoted))
+        return;
+
+    if(this._iTimeoutHideId)
+        clearTimeout(this._iTimeoutHideId);
+
+    var oPopup = this.getDoPopup();
+    if(oPopup !== false)
+        return;
+
+    this._iTimeoutShowId = setTimeout(function() {
+        $(oLink).click();
+    }, this._iTimeoutShowDelay);
+};
+
+BxDolVoteReactions.prototype.onVoteOut = function(oLink)
+{
+    if($(oLink).hasClass(this._sClassDoVoted))
+        return;
+
+    if(this._iTimeoutShowId)
+        clearTimeout(this._iTimeoutShowId);
+
+    this.hideDoPopup();
+};
+
+BxDolVoteReactions.prototype.getDoPopup = function()
+{
+    var oPopup = $('#' + this._aHtmlIds['do_popup'] + ':visible');
+    return oPopup.length > 0 && oPopup.hasClass('bx-popup-applied') ? oPopup : false;
+};
+
+BxDolVoteReactions.prototype.toggleDoPopup = function(oLink, iValue)
+{
+    var $this = this;
+    var oParams = this._getDefaultParams();
+    oParams['action'] = 'GetDoVotePopup';
+
+    if(this._iTimeoutShowId)
+        clearTimeout(this._iTimeoutShowId);
+
+    $(oLink).dolPopupAjax({
+        id: {value: this._aHtmlIds['do_popup'], force: true}, 
+        url: bx_append_url_params(this._sActionsUri, oParams),
+        value: iValue,
+        onShow: function(oPopup) {
+            $this.onDoPopupShow(oPopup);
+        },
+        onHide: function(oPopup) {
+            $this.onDoPopupHide(oPopup);
+        }
+    });
+};
+
+BxDolVoteReactions.prototype.hideDoPopup = function()
+{
+    var oPopup = this.getDoPopup();
+    if(!oPopup)
+        return;
+
+    this._iTimeoutHideId = setTimeout(function() {
+        oPopup.dolPopupHide();
+    }, this._iTimeoutHideDelay);
+};
+
+BxDolVoteReactions.prototype.onDoPopupShow = function(oPopup)
+{
+    var $this = this;
+
+    this._fOnDoPopupIn = function() {
+        $this.onDoPopupIn(this);
+    };
+
+    this._fOnDoPopupOut = function() {
+        $this.onDoPopupOut(this);
+    };
+
+    $(oPopup).hover(this._fOnDoPopupIn, this._fOnDoPopupOut);
+};
+
+BxDolVoteReactions.prototype.onDoPopupHide = function(oPopup)
+{
+    $(oPopup).unbind('mouseenter', this._fOnDoPopupIn).unbind('mouseleave', this._fOnDoPopupOut);
+};
+
+BxDolVoteReactions.prototype.onDoPopupIn = function(oPopup)
+{
+    if(this._iTimeoutHideId)
+        clearTimeout(this._iTimeoutHideId);
+};
+
+BxDolVoteReactions.prototype.onDoPopupOut = function(oPopup)
+{
+    this.hideDoPopup();
+};
+
+BxDolVoteReactions.prototype.toggleByPopup = function(oLink, sReaction)
+{
+    var oParams = this._getDefaultParams();
+    oParams['action'] = 'GetVotedBy';
+    if(sReaction)
+        oParams['reaction'] = sReaction;
+
+    $(oLink).dolPopupAjax({
+        id: {value: this._aHtmlIds['by_popup'], force: true}, 
+        url: bx_append_url_params(this._sActionsUri, oParams),
+        removeOnClose: true
+    });
+};
+
+BxDolVoteReactions.prototype.changeVotedBy = function(oLink, sReaction)
+{
+    var oContent = $('#' + this._aHtmlIds['by_popup'] + ' .' + this._sSP + '-bls-content');
+    if(oContent.length > 0)
+        oContent.children(':visible').hide().siblings('.' + this._sSP + '-bl-' + sReaction).show();
+};
+
+BxDolVoteReactions.prototype._getCounter = function(oElement)
+{
+    var oCounter = BxDolVote.prototype._getCounter.call(this, oElement);
+    if(oCounter && oCounter.length > 0)
+        return oCounter;
+//.parent(':visible')
+    return $('.' + this._aHtmlIds['counter']).find('.' + this._sSP + '-counter');
+};
+
+/** @} */

+ 1131 - 0
upgrade/files/11.0.1-11.0.2/files/inc/js/functions.js

@@ -0,0 +1,1131 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+function processJsonData(oData) {
+	var fContinue = function(oData) {
+		if(oData && oData.reload != undefined && parseInt(oData.reload) == 1)
+	    	document.location = document.location;
+
+	    if(oData && oData.redirect != undefined && oData.redirect.length != 0)
+	    	document.location = oData.redirect;
+
+	    if(oData && oData.popup != undefined) {
+	    	var oPopup = null;
+	    	var oOptions = {
+	            fog: {
+					color: '#fff',
+					opacity: .7
+	            },
+	            closeOnOuterClick: false
+	        };
+
+	    	if(typeof(oData.popup) == 'object') {
+	    		oOptions = $.extend({}, oOptions, oData.popup.options);
+	    		oPopup = $(oData.popup.html);
+	    	}
+	    	else 
+	    		oPopup = $(oData.popup);
+
+            if ('undefined' !== typeof(bx_editor_remove_all))
+                bx_editor_remove_all($('#' + oPopup.attr('id')));
+            
+	    	$('#' + oPopup.attr('id')).remove();
+	        oPopup.hide().prependTo('body').bxProcessHtml().dolPopup(oOptions);
+	    }
+
+	    if(oData && oData.form != undefined && oData.form_id != undefined)
+	    	$('form#' + oData.form_id).replaceWith(oData.form).bxProcessHtml();
+
+	    if (oData && oData.eval != undefined)
+	        eval(oData.eval);
+	};
+
+	if(oData && oData.object != undefined && oData.grid != undefined)
+		if(glGrids[oData.object] != undefined)
+			glGrids[oData.object].processJson(oData);
+
+	if(oData && oData.message != undefined && oData.message.length != 0)
+		bx_alert(oData.message, function() {
+			fContinue(oData);
+		});
+	else if(oData && oData.msg != undefined && oData.msg.length != 0)
+		bx_alert(oData.msg, function() {
+			fContinue(oData);
+		});
+	else
+		fContinue(oData);
+};
+
+function getHtmlData( elem, url, callback, method , confirmation)
+{
+    var fPerform = function() {
+		// in most cases it is element ID, in other cases - object of jQuery
+		if (typeof elem == 'string')
+		    elem = '#' + elem; // create selector from ID
+
+		var $block = $(elem);
+
+		var blockPos = $block.css('position');
+
+		$block.css('position', 'relative'); // set temporarily for displaying "loading icon"
+
+		bx_loading_content($block, true);
+		var $loadingDiv = $block.find('.bx-loading-ajax');
+
+		var iLeftOff = parseInt(($block.innerWidth() / 2.0) - ($loadingDiv.outerWidth()  / 2.0));
+		var iTopOff  = parseInt(($block.innerHeight() / 2.0) - ($loadingDiv.outerHeight()));
+		if (iTopOff<0) iTopOff = 0;
+
+		$loadingDiv.css({
+		    position: 'absolute',
+		    left: iLeftOff,
+		    top:  iTopOff
+		});
+
+		if (undefined != method && (method == 'post' || method == 'POST')) {
+		    $.post(url, function(data) {
+		        $block.html(data);
+		        $block.css('position', blockPos).bxProcessHtml();
+
+		        if (typeof callback == 'function')
+		            callback.apply($block);
+		    });		
+		} 
+		else {
+		    $block.load(url + '&_r=' + Math.random(), function() {
+		        $(this).css('position', blockPos).bxProcessHtml();
+
+		        if (typeof callback == 'function')
+		            callback.apply(this);
+		    });
+		}
+    };
+
+    if (typeof(confirmation) != 'undefined' && confirmation)
+    	bx_confirm(_t('_Are_you_sure'), fPerform);
+    else 
+    	fPerform();
+}
+
+function loadDynamicBlockAutoPaginate (e, iStart, iPerPage, sAdditionalUrlParams) {
+
+    sUrl = location.href;
+
+    sUrl = sUrl.replace(/start=\d+/, '').replace(/per_page=\d+/, '').replace(/[&\?]+$/, '');
+    sUrl = bx_append_url_params(sUrl, 'start=' + parseInt(iStart) + '&per_page=' + parseInt(iPerPage));
+    if ('undefined' != typeof(sAdditionalUrlParams))
+        sUrl = bx_append_url_params(sUrl, sAdditionalUrlParams);
+
+    return loadDynamicBlockAuto(e, sUrl);
+}
+
+/**
+ * This function reloads page block automatically, 
+ * just provide any element inside the block and this function will reload the block.
+ * @param e - element inside the block
+ * @return true on success, or false on error - particularly, if block isn't found
+ */
+function loadDynamicBlockAuto(e, sUrl) {
+	var oContainer = $(e).parents('.bx-page-block-container:first');
+    var sId = oContainer.attr('id');
+
+    if ('undefined' == typeof(sUrl))
+        sUrl = location.href;
+
+    if (!sId || !sId.length)
+        return false;
+
+    var aMatches = sId.match(/\-(\d+)$/);
+    if (!aMatches || !aMatches[1])
+        return false;
+
+    var oPaginate = oContainer.find('.bx-paginate'); 
+    if(oPaginate.length > 0) {
+    	var iStart = parseInt(oPaginate.attr('bx-data-start'));
+    	var iPerPage = parseInt(oPaginate.attr('bx-data-perpage'));
+
+    	var oParams = {};
+    	if(!isNaN(iStart) && sUrl.indexOf('start') == -1)
+    		oParams.start = iStart;
+    	if(!isNaN(iPerPage) && sUrl.indexOf('per_page') == -1)
+    		oParams.per_page = iPerPage;
+
+    	sUrl = bx_append_url_params(sUrl, oParams);
+    }
+
+    loadDynamicBlock(parseInt(aMatches[1]), sUrl);
+    return true;
+}
+
+function loadDynamicBlock(iBlockID, sUrl) {
+    
+    var oCallback = null;
+    if($('#bx-page-block-' + iBlockID + ' .bx-base-unit-showcase-wrapper').length){
+        oCallback = bx_showcase_view_init;
+    }
+    getHtmlData($('#bx-page-block-' + iBlockID), bx_append_url_params(sUrl, 'dynamic=tab&pageBlock=' + iBlockID), oCallback);
+    return true;
+}
+
+function loadDynamicPopupBlock(iBlockID, sUrl) {
+    if (!$('#dynamicPopup').length) {
+        $('<div id="dynamicPopup" style="display:none;"></div>').prependTo('body');
+    }
+
+    $('#dynamicPopup').load(
+        (sUrl + '&dynamic=popup&pageBlock=' + iBlockID),
+        function() {
+            $(this).dolPopup({
+                left: 0,
+                top: 0
+            });
+        }
+    );
+}
+
+function closeDynamicPopupBlock() {
+    $('#dynamicPopup').dolPopupHide();
+}
+
+
+/**
+ * Translate string
+ */
+function _t(s, arg0, arg1, arg2) {
+    if (!window.aDolLang || !aDolLang[s])
+        return s;
+
+    cs = aDolLang[s];
+    cs = cs.replace(/\{0\}/g, arg0);
+    cs = cs.replace(/\{1\}/g, arg1);
+    cs = cs.replace(/\{2\}/g, arg2);
+    return cs;
+}
+
+function showPopupAnyHtml(sUrl, sId) {
+
+    var oPopupOptions = {};
+
+    if (!sId || !sId.length)
+        sId = 'login_div';
+
+    $('#' + sId).remove();
+    $('<div id="' + sId + '" style="display: none;"></div>').prependTo('body').load(
+        sUrl.match('^http[s]{0,1}:\/\/') ? sUrl : sUrlRoot + sUrl,
+        function() {
+            $(this).dolPopup(oPopupOptions);
+        }
+    );
+}
+
+function bx_loading_svg(sType, sClass) {
+	sClass = sClass != undefined && sClass.length > 0 ? sClass : '';
+
+	if(sUseSvgLoading != undefined && sUseSvgLoading.length > 0)
+		return sUseSvgLoading.replace(new RegExp('__type__','g'), sType).replace(new RegExp('__class__','g'), sClass);
+
+	var s = '';
+	s += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 520 520" class="' + sClass + '" style="enable-background:new 0 0 520 520;" xml:space="preserve">';
+	s += '<g class="' + sType + '">';
+	s += '<g class="inner">';
+	s += '<path class="p1" d="M152,260C152,260,152,260,152,260c0-59.8,48.4-108.4,108.1-108.4c43.3,0,80.9,25.6,97.9,62.4V107.3c-28-18.3-61.9-28.9-98.1-28.9C159.8,78.4,78.6,159.9,78.6,260c0,59.5,28.4,112.4,73.4,145.6V260z"/>';
+	s += '<path class="p2" d="M368,114.4V260c0,59.8-48.4,108.4-108.1,108.4c-43.3,0-80.9-25.6-97.9-62.4v106.7c28,18.3,61.9,28.9,98.1,28.9c100.1,0,181.3-81.4,181.3-181.6C441.4,200.5,413,147.6,368,114.4z"/>';
+	s += '</g>';
+	s += '<g class="outer">';
+	s += '<path class="p1" d="M146.9,106.7c-8.1-15-36.9-29.9-68.9-27.3v124.2C90,164.3,114.6,130.5,146.9,106.7z"/>';
+	s += '<path class="p2" d="M373.1,413.3c8.1,15,36.7,29.9,68.9,27.3V316.4C429.8,355.7,405.4,389.5,373.1,413.3z"/>';
+	s += '</g>';
+	s += '</g>';
+	s += '</svg>';
+	return s;
+}
+
+function bx_loading_animate (e, aOptions) {
+    e = $(e);
+    if (!e.length)
+        return false;
+    if (e.find('.bx-sys-spinner').length)
+        return false;
+    return new Spinner(aOptions).spin(e.get(0));
+}
+
+function bx_loading_btn (oElement, bEnable) {
+    var oButton = $(oElement);
+    var sClassHeight = oButton.hasClass('bx-btn-small') ? 'bx-btn-small-height' : 'bx-btn-height';
+
+    if (oButton.children('div').size())
+        oButton = oButton.children('div').first();
+
+    if(!bEnable)
+    	oButton.find('.bx-loading-ajax-btn').remove();
+    else if (!oButton.find('.bx-loading-ajax-btn').length)
+    	oButton.append('<b class="bx-loading-ajax-btn ' + sClassHeight + '">' + (bUseSvgLoading ? bx_loading_svg('colored', sClassHeight) : '') + '</b>');
+
+    if(!bUseSvgLoading)
+    	bx_loading_animate(oButton.find('.bx-loading-ajax-btn'), aSpinnerSmallOpts);    
+}
+
+function bx_loading_content (oElement, bEnable, isReplace) {
+    var oBlock = $(oElement);
+    var oLoading = $('<div class="bx-loading-ajax bx-def-z-index-front">' + (bUseSvgLoading ? bx_loading_svg('colored') : '') + '</div>');
+    
+    if(!bEnable)
+    	oBlock.find(".bx-loading-ajax").remove();
+    else if(!oBlock.find('.bx-loading-ajax').length) {
+        if('undefined' != typeof(isReplace) && isReplace)
+            oBlock.html(oLoading.addClass('static'));
+        else
+            oBlock.append(oLoading);
+
+        if(!bUseSvgLoading)
+        	bx_loading_animate(oBlock.find('.bx-loading-ajax'), aSpinnerOpts);
+    } 
+}
+
+function bx_loading (elem, b) {
+
+    if (typeof elem == 'string')
+        elem = '#' + elem;
+
+    var block = $(elem);
+
+    if (block.hasClass('bx-btn')) {
+        bx_loading_btn (block, b);
+        return;
+    }
+
+    if (1 == b || true == b) {
+
+        bx_loading_content(block, b);
+
+        e = block.find(".bx-loading-ajax");
+        e.css('left', parseInt(block.width()/2.0 - e.width()/2.0));
+
+        var he = e.outerHeight();
+        var hc = block.outerHeight();
+
+        if (block.css('position') != 'relative' && block.css('position') != 'absolute') {
+            if (!block.data('css-save-position'))
+                block.data('css-save-position', block.css('position'));
+            block.css('position', 'relative');
+        }
+
+        if (hc > he) {
+            e.css('top', parseInt(hc/2.0 - he/2.0));
+        }
+
+        if (hc < he) {
+            if (!block.data('css-save-min-height'))
+                block.data('css-save-min-height', block.css('min-height'));
+            block.css('min-height', he);
+            e.css('top', 0);
+        }
+
+    } else {
+
+        if (block.data('css-save-position'))
+            block.css('position', block.data('css-save-position'));
+
+        if (block.data('css-save-min-height'))
+            block.css('min-height', block.data('css-save-min-height'));
+
+        bx_loading_content(block, b);
+
+    }
+
+}
+
+
+/**
+ * Center content with floating blocks.
+ * sSel - jQuery selector of content to be centered
+ * sBlockSel - jquery selector of blocks
+ */
+function bx_center_content (sSel, sBlockStyle, bListenOnResize) {
+
+    var sId;
+    if ($(sSel).parent().hasClass('bx-center-content-wrapper')) {
+        sId = $(sSel).parent().attr('id');
+    } else {
+        sId = 'id' + (new Date()).getTime();
+        $(sSel).wrap('<div id="'+sId+'" class="bx-center-content-wrapper"></div>');
+    }
+
+    var eCenter = $('#' + sId);
+    var iAll = $('#' + sId + ' ' + sBlockStyle).size();
+    var iWidthUnit = $('#' + sId + ' ' + sBlockStyle + ':first').outerWidth(true);
+    var iWidthContainer = eCenter.innerWidth();           
+    var iPerRow = parseInt(iWidthContainer/iWidthUnit);
+    var iLeft = (iWidthContainer - (iAll > iPerRow ? iPerRow * iWidthUnit : iAll * iWidthUnit)) / 2;
+
+    if (iWidthUnit > iWidthContainer)
+        return;
+
+    if ('undefined' != typeof(bListenOnResize) && bListenOnResize) {
+        $(window).on('resize.bx-center-content', function () {
+            bx_center_content(sSel, sBlockStyle);
+        });
+    }
+
+    eCenter.css("padding-left", iLeft);
+}
+
+/**
+ * Show pointer popup with menu from URL.
+ * @param o - menu object name
+ * @param e - element to show popup at
+ * @param options - popup options
+ * @param vars - additional GET variables
+ */
+function bx_menu_popup (o, e, options, vars) {
+    var options = options || {};
+    var vars = vars || {};
+    var o = $.extend({}, $.fn.dolPopupDefaultOptions, {id: o, url: bx_append_url_params('menu.php', $.extend({o:o}, vars))}, options);
+    if ('undefined' == typeof(e))
+        e = window;
+    $(e).dolPopupAjax(o);
+}
+
+/**
+ * Show pointer popup with menu from existing HTML.
+ * @param jSel - jQuery selector for html to show in popup
+ * @param e - element to show popup at
+ * @param options - popup options
+ */
+function bx_menu_popup_inline (jSel, e, options) {
+    var options = options || {};
+    var o = $.extend({}, $.fn.dolPopupDefaultOptions, options, {pointer:{el:$(e)}});
+    if ($(jSel + ':visible').length) 
+        $(jSel).dolPopupHide(); 
+    else 
+        $(jSel).dolPopup(o);
+}
+
+/**
+ * Show pointer popup with menu from URL.
+ * @param sObject - menu object name
+ * @param oElement - element to show popup at
+ * @param oOptions - popup options
+ * @param oVars - additional GET variables
+ */
+function bx_menu_slide (sObject, oElement, sPosition, oOptions, oVars) {
+	var oVars = oVars || {};
+    var oOptions = oOptions || {};
+    oOptions = $.extend({}, {parent: 'body', container: '.bx-sliding-menu-content'}, oOptions);
+
+    var sId = '';
+    var sIdSel = '';
+    var sIdPfx = 'bx-sliding-menu-wrapper-';
+    if(typeof(oOptions.id) != 'undefined')
+    	switch(typeof(oOptions.id)) {
+    		case 'string':
+    			sId = sIdPfx + oOptions.id;
+    			break;
+
+    		case 'object':
+    			sId = typeof(oOptions.id.force) != 'undefined' && oOptions.id.force ? oOptions.id.value : sIdPfx + oOptions.id.value;
+    			break;
+    	}
+    else
+    	sId = sIdPfx + parseInt(2147483647 * Math.random());
+
+    sIdSel = '#' + sId;
+    
+    //--- If slider exists
+    if ($(sIdSel).length) {
+    	bx_menu_slide_inline (sIdSel, oElement, sPosition);
+    	return;
+    }
+    //--- If slider doesn't exists
+    else {
+    	var oMenuLoading = $('#bx-sliding-menu-loading');
+        if(!oMenuLoading || !oMenuLoading.length)
+            return;
+
+    	$('<div id="' + sId + '" style="display:none;">' + oMenuLoading.html() + '</div>').addClass(oMenuLoading.attr('class')).appendTo(oOptions.parent);
+
+        var oLoading = $(sIdSel + ' .bx-sliding-menu-loading');
+        bx_loading_content(oLoading, true, true);
+
+        bx_menu_slide_inline (sIdSel, oElement, sPosition);
+        
+        var fOnLoad = function() {
+        	bx_loading_content(oLoading, false);
+
+        	$(sIdSel).bxProcessHtml().show();
+        };
+
+        $(sIdSel).find(oOptions.container).load(bx_append_url_params('menu.php', $.extend({o:sObject}, oVars)), function () {
+            var f = function () {
+            	if($(sIdSel).find('img').length > 0 && !$(sIdSel).find('img').get(0).complete)
+            		$(sIdSel).find('img').load(fOnLoad);
+            	else
+            		fOnLoad();
+            };
+            setTimeout(f, 100); // TODO: better way is to check if item is animating before positioning it in the code where popup is positioning
+        });
+    }
+    	
+}
+
+/**
+ * Show sliding menu from existing HTML.
+ * @param sMenu - jQuery object or selector for html to show in popup
+ * @param e - element to click to open/close slider
+ * @param sPosition - 'block' for sliding menu in blocks, 'site' - for sliding main menu
+ */
+function bx_menu_slide_inline (sMenu, e, sPosition) {
+    var options = options || {};
+    var eSlider = $(sMenu);    
+
+    if ('undefined' == typeof(e))
+        e = eSlider.data('data-control-btn');
+
+    var eIcon = $(e).find('.sys-icon-a');    
+
+    var fPositionElement = function (oElement) {
+        eSlider.css({
+            position: 'absolute',
+            top: oElement.outerHeight(true),
+            left: 0
+        });
+    };
+
+    var fPositionBlock = function () {
+        var eBlock = eSlider.parents('.bx-page-block-container');
+        eSlider.css({
+            position: 'absolute',
+            top: eBlock.find('.bx-db-header').outerHeight(true),
+            left: 0
+            //width: eBlock.width() TODO: Commited to test. Remove if everything is OK.
+        });
+    };
+
+    var fPositionSite = function () {
+        var eToolbar = $('#bx-toolbar');
+        eSlider.css({
+            position: 'fixed',
+            top: eToolbar.outerHeight(true) + (typeof iToolbarSubmenuTopOffset != 'undefined' ? parseInt(iToolbarSubmenuTopOffset) : 0),
+            left: 0
+        });
+    };
+
+    var fClose = function () {
+        if (eIcon.length)
+            (new Marka(eIcon[0])).set(eIcon.attr('data-icon-orig'));
+        eSlider.slideUp()
+    };
+
+    var fOpen = function () {
+        if (eIcon.length) {
+            eIcon.attr('data-icon-orig', eIcon.attr('data-icon'));
+            (new Marka(eIcon[0])).set('times');
+        }
+
+        if(typeof sPosition == 'object')
+            fPositionElement(sPosition);
+        else if(typeof sPosition == 'string' && sPosition == 'block')
+            fPositionBlock();
+        else
+            fPositionSite();
+
+        eSlider.slideDown();
+
+        eSlider.data('data-control-btn', e);        
+    };    
+    
+    if ('undefined' == typeof(sPosition))
+        sPosition = 'block';
+
+    if (eSlider.is(':visible')) {
+        fClose();
+
+        $(document).off('click.bx-sliding-menu touchend.bx-sliding-menu');
+        $(window).off('resize.bx-sliding-menu');
+    }
+    else {
+        bx_menu_slide_close_all_opened();
+
+        $(document).off('click.bx-sliding-menu touchend.bx-sliding-menu');
+        $(window).off('resize.bx-sliding-menu');
+
+        fOpen();
+        eSlider.find('a').each(function () {
+            $(this).off('click.bx-sliding-menu');
+            $(this).on('click.bx-sliding-menu', function () {
+                fClose();
+            });
+        });
+
+        setTimeout(function () {
+
+            var iWidthPrev = $(window).width();
+            $(window).on('resize.bx-sliding-menu', function () {
+                if ($(this).width() == iWidthPrev)
+                    return;
+                iWidthPrev = $(this).width();
+                bx_menu_slide_close_all_opened();
+            });
+ 
+            $(document).on('click.bx-sliding-menu touchend.bx-sliding-menu', function (event) {
+                if ($(event.target).parents('.bx-sliding-menu, .bx-sliding-menu-main, .bx-popup-slide-wrapper, .bx-db-header').length || $(event.target).filter('.bx-sliding-menu-main, .bx-popup-slide-wrapper, .bx-db-header').length || e === event.target)
+                    event.stopPropagation();
+                else
+                    bx_menu_slide_close_all_opened();
+            });
+
+        }, 10);
+    }
+}
+
+/**
+ * Close all opened sliding menus
+ */
+function bx_menu_slide_close_all_opened () {
+    $('.bx-sliding-menu:visible, .bx-sliding-menu-main:visible, .bx-popup-slide-wrapper:visible').each(function () {
+        bx_menu_slide_inline('#' + this.id);
+    });
+}
+
+/*
+ * Note. oData.count_old and oData.count_new are also available and can be checked or used in notification popup.  
+ */
+function bx_menu_show_live_update(oData) {
+	var sSelectorAddon = '.bx-menu-item-addon';
+
+	//--- Update Child Menu Item
+	if(oData.mi_child) {
+		var oMenuItem = $('.bx-menu-object-' + oData.mi_child.menu_object + ' .bx-menu-item-' + oData.mi_child.menu_item);
+		var oMenuItemAddon = oMenuItem.find(sSelectorAddon);
+
+		if(oMenuItemAddon.length > 0)
+			oMenuItemAddon.html(oData.count_new);
+		else
+			oMenuItem.append(oData.code.replace('{count}', oData.count_new));
+
+		//+++ Check for 0 value
+		oMenuItemAddon = oMenuItem.find(sSelectorAddon);
+		if(parseInt(oData.count_new) > 0)
+			oMenuItemAddon.show();
+		else
+			oMenuItemAddon.hide();
+	}
+
+	//--- Update Parent Menu Item
+	if(oData.mi_parent) {
+		var oMenuItem = $('.bx-menu-object-' + oData.mi_parent.menu_object + ' .bx-menu-item-' + oData.mi_parent.menu_item);
+		var oMenuItemAddon = oMenuItem.find(sSelectorAddon);
+
+		var iSum = 0;
+		$('.bx-menu-object-' + oData.mi_child.menu_object + ' .bx-menu-item .bx-menu-item-addon').each(function() {
+			iValue = parseInt($(this).html());
+			if(iValue && iValue > 0)
+				iSum += iValue;
+		});
+
+		if(oMenuItemAddon.length > 0)
+			oMenuItemAddon.html(iSum);
+		else
+			oMenuItem.append(oData.code.replace('{count}', iSum));
+
+		//+++ Check for 0 value
+		oMenuItemAddon = oMenuItem.find(sSelectorAddon);
+		if(iSum > 0)
+			oMenuItemAddon.show();
+		else
+			oMenuItemAddon.hide();
+	}
+}
+/**
+ * Set ACL level for specified profile
+ * @param iProfileId - profile id to set acl level for
+ * @param iAclLevel - acl level id to assign to a given rofile
+ */
+function bx_set_acl_level (iProfileId, iAclLevel, mixedLoadingElement) {
+    var bBulk = !$.isNumeric(iProfileId);
+    var iAclCard = !bBulk && $('#sys-acl-profile-' + iProfileId).length > 0 ? 1 : 0;
+
+    var bLoading = typeof(mixedLoadingElement) != 'undefined';
+    if(bLoading)
+        bx_loading($(mixedLoadingElement), true);
+
+    $.post(sUrlRoot + 'menu.php', {o:'sys_set_acl_level', profile_id: iProfileId, acl_level_id: iAclLevel, acl_card:iAclCard}, function(oData) {
+        if(bLoading)
+            bx_loading($(mixedLoadingElement), false);
+
+        if(oData.msg != undefined && oData.msg.length) {
+            bx_alert(oData.msg);
+            return;
+        }
+
+        if($(mixedLoadingElement).hasClass('bx-popup-applied'))
+            $(mixedLoadingElement).dolPopupHide().remove();
+
+        if(typeof(oData.card) == 'object')
+            for(var iField in oData.card) {
+                var oCard = $(oData.card[iField]);
+                $('#' + oCard.attr('id')).replaceWith(oCard);
+            }
+    }, 'json');
+}
+
+/**
+ * Set ACL badge for specified content
+ * @param sModule - current module for content
+ * @param iContentId - content id to set badge for
+ * @param iBadgeId - badge id to assign to a given content
+ */
+function bx_set_badge (sModule, iContentId, iBadgeId, mixedLoadingElement) {
+   
+    var bLoading = typeof(mixedLoadingElement) != 'undefined';
+    if(bLoading)
+        bx_loading($(mixedLoadingElement), true);
+    $.post(sUrlRoot + 'menu.php', {o:'sys_set_badges', module: sModule, content_id: iContentId, badge_id: iBadgeId}, function(oData) {
+        if(bLoading)
+            bx_loading($(mixedLoadingElement), false);
+
+        if(oData.msg != undefined && oData.msg.length) {
+            bx_alert(oData.msg);
+            return;
+        }
+
+        if($(mixedLoadingElement).hasClass('bx-popup-applied'))
+            $(mixedLoadingElement).dolPopupHide().remove();
+
+        if(typeof(oData.card) == 'object')
+            for(var iField in oData.card) {
+                var oCard = $(oData.card[iField]);
+                $('#' + oCard.attr('id')).replaceWith(oCard);
+            }
+		
+		$('.bx-base-bages-container').html(oData.html);
+		
+    }, 'json');
+}
+
+
+function validateLoginForm(eForm) {
+    return true;
+}
+
+/**
+ * convert <time> tags to human readable format
+ * @param sLang - use sLang localization
+ * @param isAutoupdate - for internal usage only
+ */
+function bx_time(sLang, isAutoupdate, sRootSel) {
+    if ('undefined' == typeof(sRootSel))
+        sRootSel = 'body';
+    var iAutoupdate = 22*60*60; // autoupdate time in realtime if less than 22 hours
+    var sSel = 'time';
+
+    if (typeof(sLang) != 'undefined')
+        glBxTimeLang = sLang;
+    else if(typeof(glBxTimeLang) != 'undefined')
+        sLang = glBxTimeLang;
+
+    if(!sLang)
+        return;
+
+    if ('undefined' != typeof(isAutoupdate) && isAutoupdate)
+        sSel += '.bx-time-autoupdate';
+
+    $(sRootSel).find(sSel).each(function () {
+        var s;
+        var sTime = $(this).attr('datetime');
+        var iSecondsDiff = moment(sTime).unix() - moment().unix();
+        if (iSecondsDiff < 0)
+            iSecondsDiff = -iSecondsDiff;
+
+        if (iSecondsDiff < $(this).attr('data-bx-autoformat'))
+            s = moment(sTime).locale(sLang).fromNow(); // 'time ago' format
+        else
+            s = moment(sTime).locale(sLang).format($(this).attr('data-bx-format')); // custom format
+
+        if (iSecondsDiff < iAutoupdate)
+            $(this).addClass('bx-time-autoupdate');
+        else
+            $(this).removeClass('bx-time-autoupdate');
+
+        $(this).html(s);
+    });
+
+    if ($('time.bx-time-autoupdate').size()) {
+        setTimeout(function () {
+            bx_time(sLang, true);
+        }, 20000);
+    }
+}
+
+(function($) {
+
+    $.fn.bxTime = function() {
+        bx_time(undefined, undefined, this);
+        return this;
+    };
+
+    /**
+     * process HTML which was added dynamicly on the page
+     */ 
+    $.fn.bxProcessHtml = function (oCallback) {
+        var eElement = $(this);
+        if ('undefined' !== typeof(eElement)) {
+            // process js time
+            eElement.bxTime();
+
+            // process web forms
+            if ($.isFunction($.fn.addWebForms))
+	            eElement.addWebForms();
+            
+            // process animating icons
+        	bx_activate_anim_icons();
+
+            // process syntax hightlighing
+            if ('undefined' !== typeof(Prism) && eElement.size())
+                Prism.highlightAllUnder(eElement[0]);
+        }
+        if ('undefined' !== typeof(oCallback)) {
+            oCallback(eElement);
+        }
+
+        if (typeof glOnProcessHtml !== 'undefined' && glOnProcessHtml instanceof Array) {
+            for (var i = 0; i < glOnProcessHtml.length; i++)
+                if (typeof glOnProcessHtml[i] === "function")
+                    glOnProcessHtml[i](this);
+        }
+        
+        return this;
+    }
+
+} (jQuery));
+
+
+/**
+ * Perform connections AJAX request. 
+ * In case of error - it shows js alert with error message.
+ * In case of success - the page is reloaded.
+ * 
+ * @param sObj - connections object
+ * @param sAction - 'add', 'remove' or 'reject'
+ * @param iContentId - content id, initiator is always current logged in user
+ * @param bConfirm - show confirmation dialog
+ */
+function bx_conn_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.err) {
+		        bx_alert(data.msg);
+		    } else {
+		        if ('function' == typeof(fOnComplete))
+		            fOnComplete(data, e);
+		        else if (!loadDynamicBlockAuto(e))
+		            location.reload();
+		        else
+		            $('#bx-popup-ajax-wrapper-bx_persons_view_actions_more').remove();
+		    }
+		};
+		
+		bx_loading_btn(e, 1);
+		
+		$.ajax({
+		    dataType: 'json',
+		    url: sUrlRoot + 'conn.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 ? '?' : '&';
+
+    if(mixedParams instanceof Array) {
+    	for(var i in mixedParams)
+            sParams += i + '=' + mixedParams[i] + '&';
+        sParams = sParams.substr(0, sParams.length-1);
+    }
+    else if(mixedParams instanceof Object) {
+    	$.each(mixedParams, function(sKey, sValue) {
+    		sParams += sKey + '=' + sValue + '&';
+    	});
+        sParams = sParams.substr(0, sParams.length-1);
+    }
+    else
+        sParams += mixedParams;
+
+    return sUrl + sParams;
+}
+
+function bx_search_on_type (e, n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults, iMinLen) {
+    var oForm = $(e.target).parents(sFormSel + ':first');
+
+    if ('undefined' != typeof(e) && 13 == e.keyCode) {
+        oForm.find('input[name=live_search]').val(0);
+        oForm.submit();
+        return false;
+    }
+
+    iMinLen = typeof(iMinLen) != 'undefined' ? parseInt(iMinLen) : 0;
+    if(iMinLen > 0 && iMinLen > (oForm.find('input[name=keyword]').val().length + 1))
+        return true;
+
+    if ('undefined' != typeof(glBxSearchTimeoutHandler) && glBxSearchTimeoutHandler)
+        clearTimeout(glBxSearchTimeoutHandler);
+
+    glBxSearchTimeoutHandler = setTimeout(function () {
+        bx_search (n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults);
+    }, 500);
+
+    return true;
+}
+
+function bx_search (n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults) {
+    if ('undefined' == typeof(sLoadingContSel))
+        sLoadingContSel = sResultsContSel;
+    if ('undefined' == typeof(bSortResults))
+        bSortResults = false;
+
+    var sQuery = $('input', sFormSel).serialize();
+
+    bx_loading($(sLoadingContSel), true);
+    $.post(sUrlRoot + 'searchKeywordContent.php', sQuery, function(data) {
+        bx_loading($(sLoadingContSel), false);
+        if (bSortResults) {
+            var aSortedUnits = $(data).find(".bx-def-unit-live-search").toArray().sort(function (a, b) {
+                return b.getAttribute('data-ts') - a.getAttribute('data-ts');
+            });
+            data = '';
+            $.each(aSortedUnits.slice(0, n), function (i, e) {
+                data += e.outerHTML;
+            });
+        } 
+        $(sResultsContSel).html(data).bxProcessHtml();
+    });
+
+    return false;
+}
+
+function on_filter_apply(e, sInputId, sFilterName)
+{
+	var oRegExp = new RegExp("[&]{0,1}" + sFilterName + "=.*");
+    var s = ('' + document.location).replace(oRegExp, ''); // remove filter
+    s = s.replace(/page=\d+/, 'page=1'); // goto 1st page
+    if (e.checked && $('#' + sInputId).val().length > 2)
+        s += (-1 == s.indexOf('?') ? '?' : '&') + sFilterName + '=' + $('#' + sInputId).val(); // append filter
+    document.location = s;
+}
+
+function on_filter_key_up (e, sCheckboxId)
+{
+    if (13 == e.keyCode) {
+        $('#' + sCheckboxId).click();
+        return false;
+    }
+    else {
+        $('#' + sCheckboxId).removeAttr('checked');
+        return true;
+    }
+}
+
+function on_copyright_click()
+{
+	oDate = new Date();
+
+	$(document).dolPopupAlert({
+		message: _t('_copyright', oDate.getFullYear())
+	});
+}
+
+function bx_activate_anim_icons(sColor)
+{
+    if(typeof(sColor) != 'undefined')
+        glBxAnimIconColor = sColor;
+    else if(typeof(glBxAnimIconColor) != 'undefined')
+        sColor = glBxAnimIconColor;
+
+    if(!sColor)
+        return;
+
+    $('.sys-icon-a').not('.marka').each(function () {
+        var e = $(this);
+        var m = new Marka(e.get(0)), r = e.attr('data-rotate'), c = e.attr('data-color');
+        m.set(e.attr('data-icon')).rotate(r ? r : 'down').color(c ? c : sColor);
+    });
+}
+
+function bx_get_style(sUrl)
+{
+	$("head").append($("<link rel='stylesheet' href='" + sUrl + "' type='text/css' />"));
+}
+
+function bx_get_param(s) {
+    if (!window.aDolOptions || !aDolOptions[s])
+        return false;
+
+    return aDolOptions[s];
+}
+
+function bx_autocomplete_fields(iId, sUrl, sName, bShowImg, bOnlyOnce, onSelect){
+	if (!$('#' + iId).hasClass('bx-form-input-autotoken'))
+			$('#' + iId).addClass('bx-form-input-autotoken');
+	
+	$('#' + iId + ' input[type=text]').autocomplete({
+		source: sUrl,
+		select: function(e, ui) {
+			$(this).val(ui.item.label);
+			$(this).trigger('superselect', ui.item);
+					
+			if (typeof onSelect === 'function')
+				onSelect(ui);
+			
+			e.preventDefault();			
+		}
+	});
+	if ($('#' + iId + ' .val').length > 0 && bOnlyOnce)
+		$('#' + iId + ' input[type=text]').hide();
+	$('#' + iId + ' input[type=text]').on('superselect', function(e, item) {
+		$(this).hide();
+		if ('undefined' != typeof(item)) {
+			var sImage = '';
+			if(bShowImg && item.unit != undefined)
+				sImage = item.unit;
+			else if(bShowImg && item.thumb != undefined)
+				sImage = '<img class="bx-def-thumb bx-def-thumb-size bx-def-margin-sec-right" src="' + item.thumb + '">';
+
+			$(this).before('<b class="bx-def-color-bg-hl bx-def-round-corners">' + sImage + item.label + '<input type="hidden" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" name="' + sName + (bOnlyOnce ? '' : '[]') + '" value="' + item.value + '" /></b>');
+		}
+
+		if (!bOnlyOnce)
+		{
+			fAutoShrinkInput();
+			$(this).show();
+			this.value = '';
+		}
+
+	}).on('keydown', function(e) {
+
+		// if: comma,enter (delimit more keyCodes with | pipe)
+		if (/(13)/.test(e.which))
+			e.preventDefault();
+
+	});
+
+	$('#' + iId).on('click', 'b', function() {
+		$(this).remove();
+			
+		if (bOnlyOnce)
+			$('#' + iId + ' input[type=text]').val('').show();
+		else
+			fAutoShrinkInput();
+	});
+
+	fAutoShrinkInput = function () {
+		var iWidthCont = $('#' + iId + '.bx-form-input-autotoken').innerWidth();
+		var iWidthExisting = 0;
+		$('#' + iId + '.bx-form-input-autotoken b').each(function () {
+			iWidthExisting += $(this).outerWidth(true);
+		});
+
+		$('#' + iId + '.bx-form-input-autotoken input').innerWidth(parseInt(iWidthCont - iWidthExisting > 180 ? iWidthCont - iWidthExisting : 180) - 5);
+	};
+
+	if (!bOnlyOnce)
+		fAutoShrinkInput();
+	else
+		$('#' + iId + '.bx-form-input-autotoken input').outerWidth('100%');
+};
+
+function bx_alert(sMessage, fOnClickOk, oParams)
+{
+    $(document).dolPopupAlert({
+        message: sMessage,
+        onClickOk: fOnClickOk,
+        params: oParams
+    });
+}
+
+function bx_confirm(sMessage, fOnClickYes, fOnClickNo, oParams)
+{
+    $(document).dolPopupConfirm({
+        message: sMessage,
+        onClickYes: fOnClickYes,
+        onClickNo: fOnClickNo,
+        params: oParams
+    });
+}
+
+function bx_prompt(sMessage, sValue, fOnClickOk, fOnClickCancel, oParams)
+{
+    $(document).dolPopupPrompt({
+        message: sMessage,
+        value: sValue,
+        onClickOk: fOnClickOk,
+        onClickCancel: fOnClickCancel,
+        params: oParams
+    });
+}
+
+/**
+ * This function loads the passed list of js files and executes callback function when the all files are loaded.
+ * In case when this function is called for dynamic loading of the same file but many times (like Plyr video init)
+ * it allows to avoid multiply loadings of the same file.
+ */
+
+function bx_get_scripts (aFiles, fCallback)
+{
+    let iCounter = 0;
+    const
+        iLength = aFiles.length,
+        fHandler = function() {
+            if (iCounter++ >= (iLength-1) && typeof(fCallback) === 'function')
+                fCallback();
+            else
+                fExecute(aFiles[iCounter]);
+        },
+        // Check if js file is not loaded yet then start to load it and returns Promise.
+        // All functions which try to load the same file will wait for loading of the file
+        // and then continue to work
+        fExecute = function(sFile) {
+            if ('undefined' === typeof(glBxLoadedScripts[sFile]))
+                glBxLoadedScripts[sFile] = { complete : $.bxGetCachedScript(sFile).done(fHandler) };
+            else
+                glBxLoadedScripts[sFile].complete.then(fHandler);
+        };
+
+    if ('undefined' === typeof(glBxLoadedScripts))
+        glBxLoadedScripts = {};
+
+    fExecute(aFiles[iCounter]);
+}
+
+jQuery.bxGetCachedScript = function(sUrl, oOptions) 
+{
+    oOptions = $.extend(oOptions || {}, {
+        dataType: "script",
+        cache: true,
+        url: sUrl
+    });
+
+    return jQuery.ajax(oOptions);
+};
+
+/** @} */

+ 12 - 0
upgrade/files/11.0.1-11.0.2/files/inc/version.inc.php

@@ -0,0 +1,12 @@
+<?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
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_VERSION', '11.0.2'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 29 - 0
upgrade/files/11.0.1-11.0.2/files/modules/base/groups/classes/BxBaseModGroupsConfig.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Groups Groups
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxBaseModGroupsConfig extends BxBaseModProfileConfig
+{
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aMenuItems2MethodsActions = array (
+            'profile-fan-add' => 'checkAllowedFanAdd',
+            'profile-fan-remove' => 'checkAllowedFanRemove',
+            'profile-subscribe-add' => 'checkAllowedSubscribeAdd',
+            'profile-subscribe-remove' => 'checkAllowedSubscribeRemove',
+            'profile-actions-more' => 'checkAllowedViewMoreMenu',
+            'convos-compose' => 'checkAllowedCompose',
+        );
+    }
+}
+
+/** @} */

+ 823 - 0
upgrade/files/11.0.1-11.0.2/files/modules/base/groups/classes/BxBaseModGroupsModule.php

@@ -0,0 +1,823 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Groups profiles module.
+ */
+class BxBaseModGroupsModule extends BxBaseModProfileModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    /**
+     * Get possible recipients for start conversation form
+     */
+    public function actionAjaxGetInitialMembers ()
+    {
+        $sTerm = bx_get('term');
+
+        $a = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo(json_encode($a));
+    }
+    
+    /**
+     * Process Process Invitation
+     */
+    public function actionProcessInvite ($sKey, $iGroupProfileId, $bAccept)
+    {
+        $aData = $this->_oDb->getInviteByKey($sKey, $iGroupProfileId);
+        if (isset($aData['invited_profile_id'])){
+            $CNF = &$this->_oConfig->CNF;
+            if (!isset($CNF['OBJECT_CONNECTIONS']) || !($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+                return '';
+            $iInvitedProfileId = $aData['invited_profile_id'];
+            if ($iInvitedProfileId != bx_get_logged_profile_id())
+                return '';
+            if ($bAccept){
+                if($oConnection && !$oConnection->isConnected($iInvitedProfileId, $iGroupProfileId)){
+                    $oConnection->addConnection($iInvitedProfileId, $iGroupProfileId);
+                    $oConnection->addConnection($iGroupProfileId, $iInvitedProfileId);
+                }
+            }
+            $this->_oDb->deleteInviteByKey($sKey, $iGroupProfileId);
+        }   
+    }
+ 
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit.html';
+
+        return parent::serviceGetSearchResultUnit($iContentId, $sUnitTemplate);
+    }
+
+    /**
+     * @see BxBaseModProfileModule::serviceGetSpaceTitle
+     */ 
+    public function serviceGetSpaceTitle()
+    {
+        return _t($this->_oConfig->CNF['T']['txt_sample_single']);
+    }
+    
+    /**
+     * @see iBxDolProfileService::serviceGetParticipatingProfiles
+     */ 
+    public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects = false)
+    {
+        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS'])){
+            $aConnectionObjects = array($this->_oConfig->CNF['OBJECT_CONNECTIONS'], 'sys_profiles_subscriptions');
+            return parent::serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects);
+        }
+        return parent::serviceGetParticipatingProfiles($iProfileId);
+    }
+    
+    /**
+     * Check if this module entry can be used as profile
+     */
+    public function serviceActAsProfile ()
+    {
+        return false;
+    }
+
+    /**
+     * Check if this module is group profile
+     */
+    public function serviceIsGroupProfile ()
+    {
+        return true;
+    }
+
+    /**
+     * check if provided profile is member of the group 
+     */ 
+    public function serviceIsFan ($iGroupProfileId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        return $this->isFan($oGroupProfile->getContentId(), $iProfileId);
+    }
+
+    /**
+     * check if provided profile is admin of the group 
+     */ 
+    public function serviceIsAdmin ($iGroupProfileId, $iProfileId = false) 
+    {
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+
+        $iGroupContentId = $oGroupProfile->getContentId();
+        if(!$this->isFan($iGroupContentId, $iProfileId))
+            return false;
+
+        $aGroupContentInfo = $this->_oDb->getContentInfoById($iGroupContentId);
+        return $this->_oDb->isAdmin($iGroupProfileId, $iProfileId, $aGroupContentInfo);
+    }
+    
+    /**
+     * Delete profile from fans and admins tables
+     * @param $iProfileId profile id 
+     */
+    public function serviceDeleteProfileFromFansAndAdmins ($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_oDb->deleteAdminsByProfileId($iProfileId);
+
+        if (isset($CNF['OBJECT_CONNECTIONS']) && ($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+            $oConnection->onDeleteInitiatorAndContent($iProfileId);
+    }
+
+    /**
+     * Reset group's author for particular group
+     * @param $iContentId group id 
+     * @return false of error, or number of updated records on success
+     */
+    public function serviceReassignEntityAuthor ($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById((int)$iContentId);
+        if (!$aContentInfo)
+            return false;
+
+        if (!($oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName())))
+            return false;
+
+        $aAdmins = $this->_oDb->getAdmins($oGroupProfile->id());
+
+        return $this->_oDb->updateAuthorById($iContentId, $aAdmins ? array_pop($aAdmins) : 0);
+    }
+
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceEntityAllActions ($mixedContent = false, $aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+                $mixedContent = array((int)$mixedContent, (method_exists($this->_oDb, 'getContentInfoById')) ? $this->_oDb->getContentInfoById((int)$mixedContent) : array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        if(!empty($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]))
+            $aParams = array_merge(array(
+                'entry_thumb' => (int)$aContentInfo[$CNF['FIELD_PICTURE']]
+            ), $aParams); 
+
+        return parent::serviceEntityAllActions ($mixedContent, $aParams);
+    }
+    
+    /**
+     * Reset group's author when author profile is deleted
+     * @param $iProfileId profile id 
+     * @return number of changed items
+     */
+    public function serviceReassignEntitiesByAuthor ($iProfileId)
+    {
+        $a = $this->_oDb->getEntriesByAuthor((int)$iProfileId);
+        if (!$a)
+            return 0;
+
+        $iCount = 0;
+        foreach ($a as $aContentInfo)
+            $iCount += ('' == $this->serviceReassignEntityAuthor($aContentInfo[$this->_oConfig->CNF['FIELD_ID']]) ? 1 : 0);
+
+        return $iCount;
+    }
+
+    public function servicePrepareFields ($aFieldsProfile)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $aFieldsProfile[$CNF['FIELD_NAME']] = $aFieldsProfile['name'];
+        $aFieldsProfile[$CNF['FIELD_TEXT']] = isset($aFieldsProfile['description']) ? $aFieldsProfile['description'] : '';
+        unset($aFieldsProfile['name']);
+        unset($aFieldsProfile['description']);
+        return $aFieldsProfile;
+    }
+
+    public function serviceOnRemoveConnection ($iGroupProfileId, $iInitiatorId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        list ($iProfileId, $iGroupProfileId, $oGroupProfile) = $this->_prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId);
+        if (!$oGroupProfile)
+            return false;
+
+        $this->_oDb->fromAdmins($iGroupProfileId, $iProfileId);
+
+        if ($oConn = BxDolConnection::getObjectInstance('sys_profiles_subscriptions'))
+            return $oConn->removeConnection($iProfileId, $iGroupProfileId);
+
+        return false;
+    }
+
+    public function serviceAddMutualConnection ($iGroupProfileId, $iInitiatorId, $bSendInviteOnly = false)
+    {        
+        $CNF = &$this->_oConfig->CNF;
+
+        list ($iProfileId, $iGroupProfileId, $oGroupProfile) = $this->_prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId);
+        if (!$oGroupProfile)
+            return false;
+
+        if (!($aContentInfo = $this->_oDb->getContentInfoById((int)BxDolProfile::getInstance($iGroupProfileId)->getContentId())))
+            return false;
+
+        if (!isset($CNF['OBJECT_CONNECTIONS']) || !($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+            return false;
+
+        $sEntryTitle = $aContentInfo[$CNF['FIELD_NAME']];
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+        // send invitation to the group 
+        if ($bSendInviteOnly && !$oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id()) && !$oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId) && bx_get_logged_profile_id() != $iProfileId) {
+
+            bx_alert($this->getName(), 'join_invitation', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array('content' => $aContentInfo, 'entry_title' => $sEntryTitle, 'entry_url' => $sEntryUrl, 'group_profile' => $iGroupProfileId, 'profile' => $iProfileId, 'notification_subobject_id' => $iProfileId, 'object_author_id' => $iGroupProfileId));
+
+        }
+        // send notification to group's admins that new connection is pending confirmation 
+        elseif (!$bSendInviteOnly && $oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id()) && !$oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId) && $aContentInfo['join_confirmation']) {
+
+            bx_alert($this->getName(), 'join_request', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId, 
+
+            	'content' => $aContentInfo, 
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl, 
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId
+            ));
+        }
+        // send notification that join request was accepted 
+        else if (!$bSendInviteOnly && $oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id(), true) && $oGroupProfile->getModule() != $this->getName() && bx_get_logged_profile_id() != $iProfileId) {
+            bx_alert($this->getName(), 'join_request_accepted', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId,
+
+            	'content' => $aContentInfo, 
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl, 
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId
+            ));
+        }
+
+        // new fan was added
+        if ($oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId, true)) {
+            // follow group on join
+            if (BxDolService::call($oGroupProfile->getModule(), 'act_as_profile')){
+                 $this->addFollower($oGroupProfile->id(), (int)$iInitiatorId);
+            }
+            else{
+                 $this->addFollower((int)$iInitiatorId, $oGroupProfile->id()); 
+            }
+            
+            bx_alert($this->getName(), 'fan_added', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId,
+
+            	'content' => $aContentInfo,
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl,
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId,
+            ));
+            
+            $this->doAudit($iGroupProfileId, $iInitiatorId, '_sys_audit_action_group_join_request_accepted');
+            
+            return false;
+        }
+
+        // don't automatically add connection (mutual) if group requires manual join confirmation
+        if ($bSendInviteOnly || $aContentInfo['join_confirmation'])
+            return false;
+
+        // check if connection already exists
+        if ($oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId, true) || $oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId))
+            return false;
+
+        if (!$oConnection->addConnection($oGroupProfile->id(), (int)$iInitiatorId))
+            return false;
+
+        return true;
+    }
+
+    public function serviceFansTable ()
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->CNF['OBJECT_GRID_CONNECTIONS']);
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+    
+	public function serviceInvitesTable ()
+    {
+		if (!isset($this->_oConfig->CNF['OBJECT_GRID_INVITES']))
+			return false;
+		
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->CNF['OBJECT_GRID_INVITES']);
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+	
+    public function serviceFans ($iContentId = 0, $bAsArray = false)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+
+        if (!($oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName())))
+            return false;
+
+        if(!$bAsArray) {
+            bx_import('BxDolConnection');
+            $mixedResult = $this->serviceBrowseConnectionsQuick ($oGroupProfile->id(), $this->_oConfig->CNF['OBJECT_CONNECTIONS'], BX_CONNECTIONS_CONTENT_TYPE_CONTENT, true);
+            if (!$mixedResult)
+                return MsgBox(_t('_sys_txt_empty'));
+        }
+        else
+            $mixedResult = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])->getConnectedContent($oGroupProfile->id(), true);
+
+        return $mixedResult;
+    }
+
+    public function serviceAdmins ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iContentId)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return false;
+
+        $iStart = (int)bx_get('start');
+        $iLimit = !empty($CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? getParam($CNF['PARAM_NUM_CONNECTIONS_QUICK']) : 4;
+        if(!$iLimit)
+            $iLimit = 4;
+        
+        $aProfiles = $this->_oDb->getAdmins($oGroupProfile->id(), $iStart,  $iLimit+1);
+        if(empty($aProfiles) || !is_array($aProfiles))
+            return false;
+
+        return $this->_serviceBrowseQuick($aProfiles, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseJoinedEntries ($iProfileId = 0, $bDisplayEmptyMsg = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+
+        return $this->_serviceBrowse ('joined_entries', array('joined_profile' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+
+    public function serviceBrowseCreatedEntries ($iProfileId = 0, $bDisplayEmptyMsg = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+
+        return $this->_serviceBrowse ('created_entries', array('author' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+    
+    public function serviceEntityInvite ($iContentId = 0, $bErrorMsg = true)
+    {
+        if (isset($this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE']))
+            return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE'], false, $bErrorMsg);
+        return false;
+    }
+    
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return false;
+
+        return parent::serviceEntitySocialSharing(array($iContentId, $aContentInfo), array(
+            'title' => $oGroupProfile->getDisplayName()
+        ));
+    }
+
+	/**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        $aSettingsTypes = array('follow_member', 'follow_context');
+        if($this->serviceActAsProfile())
+            $aSettingsTypes = array('personal', 'follow_member');
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module'),
+                array('group' => $sModule . '_vote', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'undoVote'),
+                
+                array('group' => $sModule . '_score_up', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteUp', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_up', 'module_class' => 'Module'),
+
+                array('group' => $sModule . '_score_down', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteDown', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_down', 'module_class' => 'Module'),
+
+                array('group' => $sModule . '_fan_added', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'fan_added', 'module_name' => $sModule, 'module_method' => 'get_notifications_fan_added', 'module_class' => 'Module'),
+
+                array('group' => $sModule . '_join_request', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'join_request', 'module_name' => $sModule, 'module_method' => 'get_notifications_join_request', 'module_class' => 'Module', 'module_event_privacy' => $this->_oConfig->CNF['OBJECT_PRIVACY_VIEW_NOTIFICATION_EVENT']),
+                
+                array('group' => $sModule . '_timeline_post_common', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'timeline_post_common', 'module_name' => $sModule, 'module_method' => 'get_notifications_timeline_post_common', 'module_class' => 'Module'),
+            ),
+            'settings' => array(
+                array('group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => $aSettingsTypes),
+
+                array('group' => 'score_up', 'unit' => $sModule, 'action' => 'doVoteUp', 'types' => $aSettingsTypes),
+
+                array('group' => 'score_down', 'unit' => $sModule, 'action' => 'doVoteDown', 'types' => $aSettingsTypes),
+                
+                array('group' => 'fan', 'unit' => $sModule, 'action' => 'fan_added', 'types' => $aSettingsTypes),
+
+                array('group' => 'join', 'unit' => $sModule, 'action' => 'join_request', 'types' => $aSettingsTypes),
+
+                array('group' => 'timeline_post', 'unit' => $sModule, 'action' => 'timeline_post_common', 'types' => $aSettingsTypes)
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'doVote'),
+                array('unit' => $sModule, 'action' => 'undoVote'),
+
+                array('unit' => $sModule, 'action' => 'doVoteUp'),
+                array('unit' => $sModule, 'action' => 'doVoteDown'),
+
+                array('unit' => $sModule, 'action' => 'fan_added'),
+
+                array('unit' => $sModule, 'action' => 'join_request'),
+
+                array('unit' => $sModule, 'action' => 'timeline_post_common'),
+            )
+        );
+    }
+
+    /**
+     * Notification about new member requst in the group
+     */
+    public function serviceGetNotificationsJoinRequest($aEvent)
+    {
+        return $this->_serviceGetNotification($aEvent, $this->_oConfig->CNF['T']['txt_ntfs_join_request']);
+    }
+
+	/**
+     * Notification about new member in the group
+     */
+    public function serviceGetNotificationsFanAdded($aEvent)
+    {
+        return $this->_serviceGetNotification($aEvent, $this->_oConfig->CNF['T']['txt_ntfs_fan_added']);
+    }
+
+    protected function _serviceGetNotification($aEvent, $sLangKey)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return array();
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oProfile = BxDolProfile::getInstance((int)$aEvent['subobject_id']);
+        if(!$oProfile)
+            return array();
+
+        /*
+         * Note. Group Profile URL is used for both Entry and Subentry URLs, 
+         * because Subentry URL has higher display priority and notification
+         * should be linked to Group Profile (Group Profile -> Members tab) 
+         * instead of Personal Profile of a member, who performed an action.
+         */
+        $sEntryUrl = $oGroupProfile->getUrl();
+        if(!empty($CNF['URL_ENTRY_FANS']))
+            $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_ENTRY_FANS'], array(
+                'profile_id' => $oGroupProfile->id()
+            ));
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $oGroupProfile->getDisplayName(),
+            'entry_author' => $oGroupProfile->id(),
+            'subentry_sample' => $oProfile->getDisplayName(),
+            'subentry_url' => $sEntryUrl,
+            'lang_key' => $sLangKey
+        );
+    }
+
+    /**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+        return BxBaseModGeneralModule::serviceGetTimelineData();
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $a = parent::serviceGetTimelinePost($aEvent, $aBrowseParams);
+        if($a === false)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aEvent['object_id'], $this->getName());
+
+        $a['content']['url'] = $oGroupProfile->getUrl();
+        $a['content']['title'] = $oGroupProfile->getDisplayName();
+
+        return $a;
+    }
+
+
+    // ====== PERMISSION METHODS
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $bInvited = false;
+        if (bx_get('key')){
+            $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+            $mixedInvited = $this->isInvited(bx_get('key'), $oGroupProfile->id());
+            if ($mixedInvited === true)
+                $bInvited = true;
+        }
+        
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $iProfileId) || $bInvited)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return parent::serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedCompose(&$aDataEntry, $isPerformAction = false)
+    {
+        if(!$this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]))
+            return _t('_sys_txt_access_denied');
+
+        return parent::checkAllowedCompose ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFanAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]) || !isLogged())
+            return _t('_sys_txt_access_denied');
+
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFanRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], false, true, true))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], false, true, false);
+    }
+
+    public function checkAllowedManageAdmins ($mixedDataEntry, $isPerformAction = false)
+    {
+        if (is_array($mixedDataEntry)) {
+            $aDataEntry = $mixedDataEntry;
+        }
+        else {
+            $oGroupProfile = BxDolProfile::getInstance((int)$mixedDataEntry);
+            $aDataEntry = $oGroupProfile && $this->getName() == $oGroupProfile->getModule() ? $this->_oDb->getContentInfoById($oGroupProfile->getContentId()) : array();
+        }
+
+        return parent::checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedInvite ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+    
+    public function checkAllowedChangeCover ($aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedChangeCover ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($oGroupProfile && $this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedDelete ($aDataEntry, $isPerformAction);
+    }
+    
+    public function checkAllowedJoin(&$aDataEntry, $isPerformAction = false)
+    {
+        if (bx_get('key')){
+            $sKey = bx_get('key');
+            $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+            $aData = $this->_oDb->getInviteByKey($sKey, $oGroupProfile->id());
+            if (isset($aData['invited_profile_id']) && $aData['invited_profile_id'] == bx_get_logged_profile_id()){
+                return CHECK_ACTION_RESULT_ALLOWED;
+            }
+        }   
+        return _t('_sys_txt_access_denied');
+    }   
+
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (!$this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]))
+            return _t('_sys_txt_access_denied');
+
+        return $this->_checkAllowedSubscribeAdd ($aDataEntry, $isPerformAction);
+    }
+    
+    public function _checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        return parent::checkAllowedSubscribeAdd ($aDataEntry, $isPerformAction);
+    }
+    
+    public function doAudit($iGroupProfileId, $iFanId, $sAction)
+    {
+        $oProfile = BxDolProfile::getInstance($iFanId);
+        
+        $iContentId = $oProfile->getContentId();
+        $sModule = $oProfile->getModule();
+        $oModule = BxDolModule::getInstance($sModule);
+        if (BxDolRequest::serviceExists($sModule, 'act_as_profile') && BxDolService::call($sModule, 'act_as_profile') && $oModule->_oConfig){
+            $CNF = $oModule->_oConfig->CNF;
+
+            $aContentInfo = BxDolRequest::serviceExists($sModule, 'get_all') ? BxDolService::call($sModule, 'get_all', array(array('type' => 'id', 'id' => $iContentId))) : array();
+        
+            $AuditParams = array(
+                'content_title' => (isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']])) ? $aContentInfo[$CNF['FIELD_TITLE']] : '',
+                'context_profile_id' => $iGroupProfileId,
+                'context_profile_title' => BxDolProfile::getInstance($iGroupProfileId)->getDisplayName()
+            );
+        
+            bx_audit(
+                $iContentId, 
+                $sModule, 
+                $sAction,  
+                $AuditParams
+            );
+        }
+    }
+    
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        $sResult = $this->checkAllowedView($aDataEntry);
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($this->_oConfig->CNF['OBJECT_PRIVACY_VIEW']);
+
+        // if profile view isn't allowed but visibility is in partially visible groups 
+        // then display buttons to connect (befriend, join) to profile, 
+        // if other conditions (in parent::_checkAllowedConnect) are met as well
+        if (CHECK_ACTION_RESULT_ALLOWED !== $sResult && !in_array($aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']], array_merge($oPrivacy->getPartiallyVisiblePrivacyGroups(), array('s'))))
+            return $sResult;
+
+        return parent::_checkAllowedConnect ($aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    public function addFollower ($iProfileId1, $iProfileId2)
+    {
+        $oConnectionFollow = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        if($oConnectionFollow && !$oConnectionFollow->isConnected($iProfileId1, $iProfileId2)){
+            $oConnectionFollow->addConnection($iProfileId1, $iProfileId2);
+            return true;
+        }
+        return false;
+    }
+    
+    public function isFan ($iContentId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS']))
+            return $oGroupProfile && ($oConnection = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])) && $oConnection->isConnected($iProfileId ? $iProfileId : bx_get_logged_profile_id(), $oGroupProfile->id(), true);
+        return false;
+    }
+    
+    public function isInvited ($sKey, $iProfileId) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $aData = $this->_oDb->getInviteByKey($sKey,  $iProfileId);
+        if (!isset($aData['invited_profile_id']))
+            return _t($CNF['T']['txt_invitation_popup_error_invitation_absent']);
+        
+        if ($aData['invited_profile_id'] != bx_get_logged_profile_id())
+            return  _t($CNF['T']['txt_invitation_popup_error_wrong_user']);
+        
+        return true;
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aEvent['object_id'], $this->getName());
+
+        $sSrc = '';
+        if(isset($CNF['FIELD_COVER']) && !empty($aContentInfo[$CNF['FIELD_COVER']]))
+            $sSrc = $oGroupProfile->getCover();
+
+        if(empty($sSrc) && isset($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]))
+            $sSrc = $oGroupProfile->getPicture();
+
+        return empty($sSrc) ? array() : array(
+            array('id' => $aContentInfo[$CNF['FIELD_PICTURE']], 'url' => $sUrl, 'src' => $sSrc, 'src_orig' => $sSrc),
+        );
+    }
+
+    protected function _prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId)
+    {
+        if (!($oGroupProfile = BxDolProfile::getInstance($iGroupProfileId)))
+            return array(0, 0, null);
+
+        if ($oGroupProfile->getModule() == $this->getName()) {
+            $iProfileId = $iInitiatorId;
+            $iGroupProfileId = $oGroupProfile->id();
+        } else {
+            $iProfileId = $oGroupProfile->id();
+            $iGroupProfileId = $iInitiatorId;
+        }
+
+        return array($iProfileId, $iGroupProfileId, $oGroupProfile);
+    }
+}
+
+/** @} */

+ 36 - 0
upgrade/files/11.0.1-11.0.2/files/modules/base/profile/classes/BxBaseModProfileConfig.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    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModProfileConfig extends BxBaseModGeneralConfig
+{
+    protected $_aMenuItems2MethodsActions = array();
+    protected $_aMenuItems2MethodsSubmenu = array();
+    
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aMenuItems2MethodsActions = array (
+            'profile-friend-add' => 'checkAllowedFriendAdd',
+            'profile-friend-remove' => 'checkAllowedFriendRemove',
+            'profile-relation-add' => 'checkAllowedRelationAdd',
+            'profile-relation-remove' => 'checkAllowedRelationRemove',
+            'profile-subscribe-add' => 'checkAllowedSubscribeAdd',
+            'profile-subscribe-remove' => 'checkAllowedSubscribeRemove',
+            'profile-actions-more' => 'checkAllowedViewMoreMenu',
+            'profile-set-acl-level' => 'checkAllowedSetMembership',
+            'convos-compose' => 'checkAllowedCompose',
+            'messenger' => 'checkAllowedCompose',
+        );        
+    }
+}
+
+/** @} */

+ 1847 - 0
upgrade/files/11.0.1-11.0.2/files/modules/base/profile/classes/BxBaseModProfileModule.php

@@ -0,0 +1,1847 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_DOL_PROFILE_REDIRECT_PROFILE', 'profile');
+define('BX_DOL_PROFILE_REDIRECT_LAST', 'last');
+define('BX_DOL_PROFILE_REDIRECT_CUSTOM', 'custom');
+
+/**
+ * Base class for profile modules.
+ */
+class BxBaseModProfileModule extends BxBaseModGeneralModule implements iBxDolContentInfoService, iBxDolProfileService
+{
+    protected $_iAccountId;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($CNF['FIELD_ALLOW_POST_TO']))
+            $this->_aSearchableNamesExcept[] = $CNF['FIELD_ALLOW_POST_TO'];
+
+        if(!empty($CNF['FIELD_ALLOW_CONTACT_TO']))
+            $this->_aSearchableNamesExcept[] = $CNF['FIELD_ALLOW_CONTACT_TO'];
+
+        $this->_iAccountId = getLoggedId();
+    }
+
+    public function actionDeleteProfileImg($iFileId, $iContentId, $sFieldPicture) 
+    {
+        $aResult = array();
+        $CNF = &$this->_oConfig->CNF;
+
+        $oSrorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        if (!($aFile = $oSrorage->getFile((int)$iFileId)) || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)) || $aContentInfo[$sFieldPicture] != (int)$iFileId)
+            $aResult = array('error' => 1, 'msg' => _t('_sys_storage_err_file_not_found'));
+
+        $oAccountProfile = BxDolProfile::getInstanceAccountProfile();
+        if ($oAccountProfile)
+            $iAccountProfileId = $oAccountProfile->id();
+
+        if ((!$aResult && !isLogged()) || (!$aResult && $aFile['profile_id'] != $iAccountProfileId && !$this->_isModerator()))           
+            $aResult = array('error' => 2, 'msg' => _t('_Access denied'));
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+
+        if (!$aResult && !$oForm->_deleteFile($iContentId, $sFieldPicture, (int)$iFileId, true))
+            $aResult = array('error' => 3, 'msg' => _t('_Failed'));
+        elseif (!$aResult)            
+            $aResult = array('error' => 0, 'msg' => '');
+
+        header('Content-Type: application/json; charset=utf-8');
+        echo json_encode($aResult);
+    }
+
+    // ====== SERVICE METHODS
+
+    public function serviceGetSafeServices()
+    {
+        $a = parent::serviceGetSafeServices();
+        return array_merge($a, array (
+            'ProfileUnitSafe' => '',
+            'ProfileUrl' => '',
+            'EntityCreate' => '',
+            'BrowseRecommended' => '',
+            'BrowseRecentProfiles' => '',
+            'BrowseActiveProfiles' => '',
+            'BrowseTopProfiles' => '',
+            'BrowseOnlineProfiles' => '',
+            'BrowseByAcl' => '',
+        ));
+    }
+
+    public function serviceGetOptionsRedirectAfterAdd()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = array();
+        $aChoices = array(BX_DOL_PROFILE_REDIRECT_PROFILE, BX_DOL_PROFILE_REDIRECT_LAST, BX_DOL_PROFILE_REDIRECT_CUSTOM);
+        foreach($aChoices as $sChoice) 
+            $aResult[] = array('key' => $sChoice, 'value' => _t($CNF['T']['option_redirect_aadd_' . $sChoice]));
+
+        return $aResult;
+    }
+
+    public function serviceGetThumb ($iContentId, $sTranscoder = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sTranscoder) && !empty($CNF['OBJECT_IMAGES_TRANSCODER_THUMB']))
+            $sTranscoder = $CNF['OBJECT_IMAGES_TRANSCODER_THUMB'];
+
+        $mixedResult = $this->_getFieldValueThumb('FIELD_PICTURE', $iContentId, $sTranscoder);
+        return $mixedResult !== false ? $mixedResult : '';
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit_with_cover.html';
+
+        return parent::serviceGetSearchResultUnit($iContentId, $sUnitTemplate);
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = parent::serviceGetSearchableFieldsExtended($aInputsAdd);
+        if(!$this->serviceActAsProfile())
+            return $aResult;
+
+        if(!in_array('online', $this->_aSearchableNamesExcept))
+            $aResult['online'] = array(
+                'type' => 'checkbox', 
+                'caption' => $CNF['T']['form_field_online'],
+                'info' => '',
+            	'value' => '1',
+                'values' => '',
+                'pass' => ''
+            );
+
+        if(!empty($CNF['FIELD_PICTURE']) && !in_array($CNF['FIELD_PICTURE'], $this->_aSearchableNamesExcept))
+            $aResult[$CNF['FIELD_PICTURE']] = array(
+                'type' => 'checkbox', 
+                'caption' => $CNF['T']['form_field_picture'],
+                'info' => '',
+            	'value' => '1',
+                'values' => '',
+                'pass' => '',
+                'search_operator' => '>=' 
+            );
+
+        return $aResult;
+    }
+
+	public function servicePrivateProfileMsg()
+    {
+        $mixedContent = $this->_getContent();
+        if ($mixedContent) {
+            list($iContentId, $aContentInfo) = $mixedContent;
+            return $this->checkAllowedView($aContentInfo);
+        }
+        return MsgBox(_t('_sys_access_denied_to_private_content'));
+    }
+    
+	public function serviceGetContentInfoById($iContentId)
+    {
+        return $this->_oDb->getContentInfoById((int)$iContentId);
+    }
+
+	public function serviceGetMenuAddonManageTools()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->unsetPaginate();
+        $iNumTotal = $o->getNum();
+        
+        $o->fillFilters(array(
+			'perofileStatus' => BX_PROFILE_STATUS_PENDING
+        ));
+        $iNum1 = $o->getNum();
+        
+        $iNum2 = 0;
+        $CNF = &$this->_oConfig->CNF;
+        if (isset($CNF['OBJECT_REPORTS'])){
+            $o->fillFilters(array('perofileStatus' => ''));
+            $o->fillFiltersByObjects(array('reported' => array('value' => '0', 'field' => 'reports', 'operator' => '>', 'table' => $CNF['TABLE_ENTRIES'])));
+            $iNum2 = $o->getNum();
+        }
+        return array('counter1_value' => $iNum1, 'counter2_value' => $iNum2, 'counter3_value' => $iNumTotal, 'counter1_caption' => _t('_sys_menu_dashboard_manage_tools_addon_counter1_caption_profile_default'));
+	}
+
+	public function serviceGetMenuAddonManageToolsProfileStats()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'account_id' => getLoggedId(),
+        	'perofileStatus' => ''
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+    public function serviceGetMenuAddonFavoritesProfileStats()
+    {
+    	bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass('favorite', array('user' => bx_get_logged_profile_id()));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+    }
+
+    public function serviceGetSubmenuObject ()
+    {
+        return $this->_oConfig->CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+    }
+
+    public function serviceGetMenuSetNameForMenuTrigger ($sMenuTriggerName)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if (isset($CNF['TRIGGER_MENU_PROFILE_VIEW_SUBMENU']) && $CNF['TRIGGER_MENU_PROFILE_VIEW_SUBMENU'] == $sMenuTriggerName)
+            return $CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+        elseif (isset($CNF['TRIGGER_MENU_PROFILE_SNIPPET_META']) && $CNF['TRIGGER_MENU_PROFILE_SNIPPET_META'] == $sMenuTriggerName)
+            return $CNF['OBJECT_MENU_SNIPPET_META'];
+        else if (isset($CNF['TRIGGER_MENU_PROFILE_VIEW_ACTIONS']) && $CNF['TRIGGER_MENU_PROFILE_VIEW_ACTIONS'] == $sMenuTriggerName) {
+            if(empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']))
+                return $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'];
+            else
+                return array($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'], $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']);
+        }
+
+        return '';
+    }
+
+    public function serviceGetSnippetMenuVars($iProfileId, $bPublic = null)
+    {
+        return $this->_oTemplate->getSnippetMenuVars($iProfileId, $bPublic);
+    }
+        
+	public function serviceGetPageObjectForPageTrigger ($sPageTriggerName)
+    {
+        if (isset($this->_oConfig->CNF['TRIGGER_PAGE_VIEW_ENTRY']) && $this->_oConfig->CNF['TRIGGER_PAGE_VIEW_ENTRY'] == $sPageTriggerName)
+        	return $this->_oConfig->CNF['OBJECT_PAGE_VIEW_ENTRY'];
+
+        return '';
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit)
+    {
+		$aRet = array();
+
+        $a = $this->_oDb->searchByTerm($sTerm, $iLimit);
+        foreach ($a as $r) {
+            $oProfile = BxDolProfile::getInstance($r['profile_id']);
+
+            $aRet[] = array (
+            	'label' => $this->serviceProfileName($r['content_id']), 
+                'value' => $r['profile_id'], 
+                'url' => $oProfile->getUrl(),
+            	'thumb' => $oProfile->getThumb(),
+                'unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+            );
+        }
+
+        return $aRet;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-general General
+     * @subsubsection bx_base_profile-profile_unit_safe profile_unit_safe
+     * 
+     * @code bx_srv('bx_persons', 'profile_unit_safe', [...]); @endcode
+     * 
+     * Get profile unit
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModProfileModule::serviceProfileUnitSafe
+     */
+    /** 
+     * @ref bx_base_profile-profile_unit_safe "profile_unit_safe"
+     */
+    public function serviceProfileUnitSafe ($iContentId, $aParams = array())
+    {
+        unset($aParams['check_private_content']);
+        return $this->serviceProfileUnit ($iContentId, $aParams);
+    }
+
+    public function serviceProfileUnit ($iContentId, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $bCheckPrivateContent = isset($aParams['check_private_content']) ? (bool)$aParams['check_private_content'] : true;
+
+        $sTemplate = 'unit.html';
+        $sTemplateSize = false;
+        $aTemplateVars = array();
+        if(!empty($aParams['template'])) {
+            if(is_string($aParams['template']))
+                $sTemplate = $aParams['template'] . '.html';
+            else if(is_array($aParams['template'])) {
+                if(!empty($aParams['template']['name']))
+                    $sTemplate = $aParams['template']['name'] . '.html';
+
+                if(!empty($aParams['template']['size']))
+                    $sTemplateSize = $aParams['template']['size'];
+
+                if(!empty($aParams['template']['vars']))
+                    $aTemplateVars = $aParams['template']['vars'];
+            }
+        }
+
+        return $this->_oTemplate->unit($aContentInfo, $bCheckPrivateContent, array($sTemplate, $sTemplateSize, $aTemplateVars));
+    }
+
+    public function serviceHasImage ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedContent = $this->_getContent($iContentId, 'getContentInfoById');
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        return !empty($aContentInfo[$CNF['FIELD_PICTURE']]);
+    }
+
+    public function serviceProfilePicture ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlPicture', $iContentId);
+    }
+
+    public function serviceProfileAvatar ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlAvatar', $iContentId);
+    }
+
+    public function serviceProfileAvatarBig ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlAvatarBig', $iContentId);
+    }
+
+    public function serviceProfileCover ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlCover', $iContentId);
+    }
+
+    public function serviceProfileUnitCover ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlCoverUnit', $iContentId);
+    }
+
+    public function serviceProfileEditUrl ($iContentId)
+    {
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->_oConfig->CNF['URI_EDIT_ENTRY'] . '&id=' . $iContentId);
+    }
+
+    public function serviceProfileThumb ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlThumb', $iContentId);
+    }
+
+    public function serviceProfileIcon ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlIcon', $iContentId);
+    }
+
+    public function serviceProfileName ($iContentId)
+    {
+        if (!$iContentId)
+            return false;
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+        return bx_process_output($aContentInfo[$this->_oConfig->CNF['FIELD_NAME']]);
+    }
+
+    public function serviceProfileCreateUrl ($bAbsolute = true)
+    {
+    	$CNF = $this->_oConfig->CNF;
+    	if(empty($CNF['URL_CREATE']))
+    		return false;
+
+    	return $bAbsolute ? BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_CREATE']) : $CNF['URL_CREATE'];
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-general General
+     * @subsubsection bx_base_profile-profile_url profile_url
+     * 
+     * @code bx_srv('bx_persons', 'profile_url', [...]); @endcode
+     * 
+     * Get profile URL
+     * @param $iContentId content ID
+     * 
+     * @see BxBaseModProfileModule::serviceProfileUrl
+     */
+    /** 
+     * @ref bx_base_profile-profile_url "profile_url"
+     */
+    public function serviceProfileUrl ($iContentId)
+    {
+        if (!$iContentId)
+            return false;
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+        $CNF = $this->_oConfig->CNF;
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceGetSpaceTitle
+     */ 
+    public function serviceGetSpaceTitle()
+    {
+        return _t('_sys_ps_space_title_friend');
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceGetParticipatingProfiles
+     */ 
+    public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects = false)
+    {
+        if (false === $aConnectionObjects)
+            $aConnectionObjects = array('sys_profiles_friends');
+
+        $a = array();
+        foreach ($aConnectionObjects as $sConnectionObject) {
+            if (!($o = BxDolConnection::getObjectInstance($sConnectionObject)))
+                continue;
+
+            if (BX_CONNECTIONS_TYPE_MUTUAL == $o->getType())
+                $a = array_merge($a, $o->getConnectedContent($iProfileId, true));
+            else
+                $a = array_merge($a, $o->getConnectedContent($iProfileId));
+        }
+
+        $a = array_unique($a);
+        $aRet = array();
+        foreach ($a as $iConnectedProfileId) {
+            if (!($oConnectedProfile = BxDolProfile::getInstance($iConnectedProfileId)))
+                continue;
+            if ($oConnectedProfile->getModule() != $this->getName())
+                continue;
+
+            if (CHECK_ACTION_RESULT_ALLOWED === bx_srv($oConnectedProfile->getModule(), 'check_allowed_post_in_profile', array($oConnectedProfile->getContentId(), $iProfileId)))
+                $aRet[] = $iConnectedProfileId;
+        }
+
+        bx_alert('system', 'get_participating_profiles', $iProfileId, false, array(
+            'module' => $this->_oConfig->getName(),
+            'profiles' => &$aRet
+        ));
+
+        return $aRet;
+    }
+
+    /**
+     * Prepare fields from some universal set of fields to fields in particular profile module. 
+     * By default only 'name' and 'description' fields are supported.
+     * After fields convertion it can be used in @see BxBaseModGeneralModule::serviceEntityAdd
+     * @param $aFieldsProfile fields in soem universal format.
+     * @return array which is ready to use for particular module
+     */ 
+    public function servicePrepareFields ($aFieldsProfile)
+    {        
+        bx_alert($this->getName(), 'prepare_fields', 0, 0, array('fields_orig' => $aFieldsProfile, 'fields_result' => &$aFieldsProfile));
+
+        return $aFieldsProfile;
+    }
+
+    protected function _servicePrepareFields ($aFieldsProfile, $aFieldsDefault, $aMap)
+    {
+        $aFieldsOrig = $aFieldsProfile;
+
+        bx_import('BxDolPrivacy');
+        $aFieldsDefault2 = array(
+            'allow_view_to' => BX_DOL_PG_ALL,
+            'allow_contact_to' => BX_DOL_PG_ALL, 
+            'allow_post_to' => BX_DOL_PG_FRIENDS,
+        );
+        $aFieldsProfile = array_merge($aFieldsDefault2, $aFieldsDefault, $aFieldsProfile);
+
+        foreach ($aMap as $k => $v) {
+            if (isset($aFieldsProfile[$v]))
+                $aFieldsProfile[$k] = $aFieldsProfile[$v];
+            if ($k != $v && !isset($aMap[$v]))
+                unset($aFieldsProfile[$v]);
+        }
+        
+        bx_alert($this->getName(), 'prepare_fields', 0, 0, array('fields_orig' => $aFieldsOrig, 'fields_result' => &$aFieldsProfile));
+
+        return $aFieldsProfile;
+    }
+
+    public function serviceFormsHelper ()
+    {
+        return parent::serviceFormsHelper ();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-forms Forms
+     * @subsubsection bx_base_profile-entity_create entity_create
+     * 
+     * @code bx_srv('bx_persons', 'entity_create', [...]); @endcode
+     * 
+     * Get profile creation form
+     * @param $sDisplay optional form display name
+     * 
+     * @see BxBaseModProfileModule::serviceEntityCreate
+     */
+    /** 
+     * @ref bx_base_profile-entity_create "entity_create"
+     */
+    public function serviceEntityCreate ($sDisplay = false)
+    {
+	    BxDolInformer::getInstance($this->_oTemplate)->setEnabled(false);
+
+	    return parent::serviceEntityCreate ($sDisplay);
+    }
+
+    public function serviceActAsProfile ()
+    {
+        return true;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_recommended browse_recommended
+     * 
+     * @code bx_srv('bx_persons', 'browse_recommended', [...]); @endcode
+     * 
+     * Browse recommended profiles
+     * @param $sUnitView optional, unit view, for example - showcase
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseRecommended
+     */
+    /** 
+     * @ref bx_base_profile-browse_recommended "browse_recommended"
+     */
+    public function serviceBrowseRecommended ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = true)
+    {
+        if (!isLogged())
+            return '';
+        return $this->_serviceBrowse ('recommended', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_recent_profiles browse_recent_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_recent_profiles', [...]); @endcode
+     * 
+     * Browse recently joined profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseRecentProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_recent_profiles "browse_recent_profiles"
+     */
+    public function serviceBrowseRecentProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('recent', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_active_profiles browse_active_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_active_profiles', [...]); @endcode
+     * 
+     * Browse recently active profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseActiveProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_active_profiles "browse_active_profiles"
+     */
+    public function serviceBrowseActiveProfiles ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('active', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_top_profiles browse_top_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_top_profiles', [...]); @endcode
+     * 
+     * Browse top profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseTopProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_top_profiles "browse_top_profiles"
+     */
+    public function serviceBrowseTopProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('top', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_online_profiles browse_online_profiles
+     * 
+     * @code bx_srv('bx_persons', 'browse_online_profiles', [...]); @endcode
+     * 
+     * Browse online profiles
+     * @param $bEmptyMessage optional, display or not "empty" message when there is no content
+     * @param $bAjaxPaginate optional, use AJAX paginate or not
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseOnlineProfiles
+     */
+    /** 
+     * @ref bx_base_profile-browse_online_profiles "browse_online_profiles"
+     */
+    public function serviceBrowseOnlineProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('online', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    public function serviceBrowseConnections ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iDesignBox = BX_DB_PADDING_DEF, $iProfileId2 = 0)
+    {
+        return $this->_serviceBrowse (
+            'connections',
+            array(
+                'object' => $sObjectConnections,
+                'type' => $sConnectionsType,
+                'mutual' => $iMutual,
+                'profile' => (int)$iProfileId,
+                'profile2' => (int)$iProfileId2),
+            $iDesignBox
+        );
+    }
+
+    public function serviceBrowseRelationsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        $oConnection = BxDolConnection::getObjectInstance($sObjectConnections);
+        if (!$oConnection)
+            return '';       
+
+        // set some vars
+        $iStart = (int)bx_get('start');
+        $iLimit = empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? 4 : getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']);
+        if (!$iLimit)
+            $iLimit = 4;
+
+        // get connections array
+        bx_import('BxDolConnection');
+        $aConnections = $oConnection->getConnectionsAsArrayExt($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, $iStart, $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if(empty($aConnections) || !is_array($aConnections))
+            return '';
+
+        $aResult = array();
+        foreach($aConnections as $iProfile => $aConnection)
+            $aResult[] = array(
+                'id' => $iProfile,
+                'info' => array(
+                    'addon' => $oConnection->getRelationTranslation($aConnection['relation'])
+                )
+            );
+
+        return $this->_serviceBrowseQuick($aResult, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseConnectionsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        $oConnection = BxDolConnection::getObjectInstance($sObjectConnections);
+        if (!$oConnection)
+            return '';
+
+        // set some vars
+        $iLimit = empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? 4 : getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']);
+        if (!$iLimit)
+            $iLimit = 4;
+        $iStart = (int)bx_get('start');
+
+        // get connections array
+        bx_import('BxDolConnection');
+        $a = $oConnection->getConnectionsAsArray ($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, (int)bx_get('start'), $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if (!$a)
+            return '';
+
+        return $this->_serviceBrowseQuick($a, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseConnectionsByType ($aParamsCnn, $aParamsBrs = array())
+    {
+        $sAll = 'all';
+
+        $aParamsCnn['object'] = !empty($aParamsCnn['object']) ? $aParamsCnn['object'] : 'sys_profiles_friends';
+        $aParamsCnn['type'] = !empty($aParamsCnn['type']) ? $aParamsCnn['type'] : 'content';
+        $aParamsCnn['profile_id2'] = !empty($aParamsCnn['profile_id2']) ? (int)$aParamsCnn['profile_id2'] : 0;
+        $aParamsCnn['mutual'] = isset($aParamsCnn['mutual']) ? $aParamsCnn['mutual'] : false;
+
+        $oConnection = BxDolConnection::getObjectInstance($aParamsCnn['object']);
+        if(!$oConnection)
+            return '';
+
+        $aSQLParts = $oConnection->getConnectionsAsSQLParts ($aParamsCnn['type'], 'sys_profiles', 'id', $aParamsCnn['profile_id'], $aParamsCnn['profile_id2'], $aParamsCnn['mutual']);
+
+        $sType = !empty($aParamsBrs['type']) ? $aParamsBrs['type'] : $sAll;
+        if(bx_get('type') !== false)
+            $sType = bx_process_input(bx_get('type'));
+
+        $iStart = (int)bx_get('start');
+
+        $iLimitDefault = 4;
+        $iLimit = !empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) : $iLimitDefault;
+        if(!empty($aParamsBrs['per_page']))
+            $iLimit = (int)$aParamsBrs['per_page'];
+        if(!$iLimit)
+            $iLimit = $iLimitDefault;
+
+        $aProfiles = BxDolProfileQuery::getInstance()->getConnectedProfilesByType($aSQLParts, ($sType != $sAll ? $sType : ''), $iStart, $iLimit + 1);
+
+        $mixedMenu = '';
+        if(!isset($aParamsBrs['filter_menu']) || $aParamsBrs['filter_menu'] === true) {
+            $aModules = array(
+                array('name' => $sAll)
+            );
+            $aModules = array_merge($aModules, BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1)));
+
+            $aMenuItems = array();
+            foreach($aModules as $aModule)
+                if($aModule['name'] == $sAll || BxDolRequest::serviceExists($aModule['name'], 'act_as_profile'))
+                    $aMenuItems[] = array(
+                    	'id' => $aModule['name'], 
+                    	'name' => $aModule['name'], 
+                    	'class' => '', 
+                    	'link' => 'javascript:void(0)', 
+                    	'onclick' => "return !loadDynamicBlockAutoPaginate(this, " . $iStart . ", " . $iLimit . ", " . bx_js_string(json_encode(array('type' => $aModule['name']))) . ");", 
+                    	'target' => '_self', 
+                    	'title' => _t('_' . $aModule['name']), 
+                    	'active' => 1
+                    );
+    
+            if(!empty($aMenuItems)) {
+                $mixedMenu = new BxTemplMenu(array('template' => 'menu_vertical.html', 'menu_id'=> $this->_oConfig->getName() . '-connections-by-type', 'menu_items' => $aMenuItems));
+                $mixedMenu->setSelected('', $sType);
+            }
+        }
+
+        return array(
+            'menu' => $mixedMenu,
+            'content' => $this->_serviceBrowseQuick(array_keys($aProfiles), $iStart, $iLimit, array('type' => $sType))
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_profile Base Profile
+     * @subsection bx_base_profile-browse Browse
+     * @subsubsection bx_base_profile-browse_by_acl browse_by_acl
+     * 
+     * @code bx_srv('bx_persons', 'browse_by_acl', [...]); @endcode
+     * 
+     * Browse profiles by membership(ACL) level
+     * @param $mixedLevelId membership level, or array of membership levels
+     * @param $iDesignBox optional, design box identifier
+     * 
+     * @see BxBaseModProfileModule::serviceBrowseByAcl
+     */
+    /** 
+     * @ref bx_base_profile-browse_by_acl "browse_by_acl"
+     */
+	public function serviceBrowseByAcl ($mixedLevelId, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        return $this->_serviceBrowse (
+            'acl',
+            array(
+                'level' => $mixedLevelId,
+			),
+            $iDesignBox,
+            true
+        );
+    }
+
+    public function serviceEntityEditCover ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT_COVER']);
+    }
+
+	/**
+     * Entry comments
+     */
+    public function serviceEntityCommentsByProfile ($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_COMMENTS']))
+            return '';
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        return $this->_entityComments($CNF['OBJECT_COMMENTS'], $oProfile->getContentId());
+    }
+
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;    
+
+        return parent::serviceEntitySocialSharing(array($iContentId, $aContentInfo), array(
+            'id_thumb' => !empty($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]) ? $aContentInfo[$CNF['FIELD_PICTURE']] : 0, 
+        ));
+    }
+
+    public function serviceProfileMembership ($iContentId = 0)
+    {
+    	$mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+		return BxDolAcl::getInstance()->getProfileMembership($aContentInfo['profile_id']);
+    }
+
+    public function serviceProfileFriends ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseConnectionsQuick ($aContentInfo['profile_id'], 'sys_profiles_friends', BX_CONNECTIONS_CONTENT_TYPE_CONTENT, true);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+        return $s;
+    }
+
+    public function serviceProfileSubscriptions ($iContentId = 0, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $aResult = $this->serviceBrowseConnectionsByType(array(
+        	'profile_id' => $aContentInfo['profile_id'],  
+        	'object' => 'sys_profiles_subscriptions', 
+            'type' => BX_CONNECTIONS_CONTENT_TYPE_CONTENT
+        ), array(
+            'type' => isset($aParams['type']) ? $aParams['type'] : '',
+            'filter_menu' => isset($aParams['filter_menu']) ? $aParams['filter_menu'] : true,
+        ));
+        if(empty($aResult['content']))
+            $aResult['content'] = MsgBox(_t('_sys_txt_empty'));
+
+        return $aResult;
+    }
+
+    public function serviceProfileSubscribedMe ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseConnectionsQuick ($aContentInfo['profile_id'], 'sys_profiles_subscriptions', BX_CONNECTIONS_CONTENT_TYPE_INITIATORS);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    public function serviceProfileRelations ($iContentId = 0, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseRelationsQuick ($aContentInfo['profile_id'], 'sys_profiles_relations', BX_CONNECTIONS_CONTENT_TYPE_CONTENT, 1);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    public function serviceProfileRelatedMe ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseRelationsQuick ($aContentInfo['profile_id'], 'sys_profiles_relations', BX_CONNECTIONS_CONTENT_TYPE_INITIATORS, 1);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    /**
+     * check enabled profile activation letter
+     */
+    public function  serviceIsEnableProfileActivationLetter()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return isset($CNF['PARAM_ENABLE_ACTIVATION_LETTER']) ? (bool)getParam($CNF['PARAM_ENABLE_ACTIVATION_LETTER']) : true;
+    }
+
+    public function serviceIsEnableRelations()
+    {
+        $sModule = $this->_oConfig->getName();
+        $oRelations = BxDolConnection::getObjectInstance('sys_profiles_relations');
+        return $oRelations->isRelationAvailableWithProfile($sModule) || $oRelations->isRelationAvailableFromProfile($sModule);
+    }
+
+    /**
+     * For internal usage only.
+     */
+    public function serviceDeleteEntityService ($iContentId, $bDeleteWithContent = false)
+    {
+        return parent::serviceDeleteEntity ($iContentId, 'deleteDataService');
+    }
+
+	/**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+        $a = parent::serviceGetNotificationsData();
+
+        $sModule = $this->_aModule['name'];
+        
+        $a['handlers'][] = array('group' => $sModule . '_timeline_post_common', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'timeline_post_common', 'module_name' => $sModule, 'module_method' => 'get_notifications_timeline_post_common', 'module_class' => 'Module');
+
+        $a['settings'][] = array('group' => 'timeline_post', 'unit' => $sModule, 'action' => 'timeline_post_common', 'types' => array('follow_member'));
+
+        $a['alerts'][] = array('unit' => $sModule, 'action' => 'timeline_post_common');
+
+        return $a;
+    }
+
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $aResult = parent::serviceGetNotificationsVote($aEvent);
+        if(empty($aResult) || !is_array($aResult) || !$this->serviceActAsProfile())
+            return $aResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType((int)$aEvent['object_id'], $this->_oConfig->getName());
+        if($oProfile !== false)
+            $aResult['entry_author'] = $oProfile->id();
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsVote($aEvent)
+    {
+        $aResult = parent::serviceGetNotificationsVote($aEvent);
+        if(empty($aResult) || !is_array($aResult) || !$this->serviceActAsProfile())
+            return $aResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType((int)$aEvent['object_id'], $this->_oConfig->getName());
+        if($oProfile !== false)
+            $aResult['entry_author'] = $oProfile->id();
+
+        return $aResult;
+    }
+
+    /**
+     * Notification about new member requst in the group
+     */
+    public function serviceGetNotificationsTimelinePostCommon($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return array();
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+            
+        $aSubcontentInfo = BxDolService::call('bx_timeline', 'get_info', array((int)$aEvent['subobject_id'], false));
+        if(empty($aSubcontentInfo) || !is_array($aSubcontentInfo))
+            return array();
+
+        $sSubentryUrl = BxDolService::call('bx_timeline', 'get_link', array((int)$aEvent['subobject_id']));
+        $sSubentrySample = $aSubcontentInfo['title'];
+        if(empty($sSubentrySample))
+            $sSubentrySample = strmaxtextlen($aSubcontentInfo['description'], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $oGroupProfile->getUrl(),
+            'entry_caption' => $oGroupProfile->getDisplayName(),
+            'entry_author' => $oGroupProfile->id(),
+            'subentry_sample' => $sSubentrySample,
+            'subentry_url' => $sSubentryUrl,
+            'lang_key' => $CNF['T']['txt_ntfs_timeline_post_common'],
+        );
+    }
+
+	/**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sModule = $this->_aModule['name'];
+
+        $aAlerts = array();
+        $aHandlers = array();
+        if(!empty($CNF['FIELD_PICTURE'])) {
+            $aAlerts = array_merge($aAlerts, array(
+                array('unit' => $sModule, 'action' => 'profile_picture_changed'),
+                array('unit' => $sModule, 'action' => 'profile_picture_deleted')
+            ));
+            $aHandlers = array_merge($aHandlers, array(
+                array('group' => $sModule . '_profile_picture', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'profile_picture_changed', 'module_name' => $sModule, 'module_method' => 'get_timeline_profile_picture', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_profile_picture', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'profile_picture_deleted')
+            ));
+        }
+
+        if(!empty($CNF['FIELD_COVER'])) {
+            $aAlerts = array_merge($aAlerts, array(
+                array('unit' => $sModule, 'action' => 'profile_cover_changed'),
+                array('unit' => $sModule, 'action' => 'profile_cover_deleted')
+            ));
+            $aHandlers = array_merge($aHandlers, array(
+                array('group' => $sModule . '_profile_cover', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'profile_cover_changed', 'module_name' => $sModule, 'module_method' => 'get_timeline_profile_cover', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_profile_cover', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'profile_cover_deleted')
+            ));
+        }
+
+    	$aResult = array();
+        if(!empty($aAlerts) && !empty($aHandlers))
+            $aResult = array(
+            	'handlers' => $aHandlers,
+            	'alerts' => $aAlerts
+            );
+
+        return $aResult;
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelineProfilePicture($aEvent, $aBrowseParams = array())
+    {
+        $aResult = $this->_serviceGetTimelineProfileImage($aEvent, $aBrowseParams, array(
+            'stg' => 'OBJECT_STORAGE',
+            'trans' => array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_AVATAR'),
+            'trans_orig' => array('OBJECT_IMAGES_TRANSCODER_PICTURE', 'OBJECT_IMAGES_TRANSCODER_GALLERY'),
+            'txt_ss' => 'txt_sample_pp_single',
+            'txt_sswa' => 'txt_sample_pp_single_with_article',
+            'txt_sa' => 'txt_sample_pi_action',
+            'txt_sau' => 'txt_sample_pi_action_user'
+        ));
+        $aResult['allowed_view'] = array('module' => $this->_oConfig->getName(), 'method' => 'get_timeline_profile_picture_allowed_view');
+
+        return $aResult;
+    }
+
+    public function serviceGetTimelineProfilePictureAllowedView($aEvent)
+    {
+        return $this->_serviceGetTimelineProfileImageAllowedView($aEvent);
+    }
+
+    public function serviceGetTimelineProfileCover($aEvent, $aBrowseParams = array())
+    {
+        $aResult = $this->_serviceGetTimelineProfileImage($aEvent, $aBrowseParams, array(
+            'stg' => 'OBJECT_STORAGE_COVER',
+            'trans' => array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_COVER_THUMB'),
+            'trans_orig' => array('OBJECT_IMAGES_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_GALLERY'),
+            'txt_ss' => 'txt_sample_pc_single',
+            'txt_sswa' => 'txt_sample_pc_single_with_article',
+            'txt_sa' => 'txt_sample_pi_action',
+            'txt_sau' => 'txt_sample_pi_action_user'
+        ));
+        $aResult['allowed_view'] = array('module' => $this->_oConfig->getName(), 'method' => 'get_timeline_profile_cover_allowed_view');
+
+        return $aResult;
+    }
+    
+    public function serviceGetTimelineProfileCoverAllowedView($aEvent)
+    {
+        return $this->_serviceGetTimelineProfileImageAllowedView($aEvent);
+    }
+
+    public function serviceGetConnectionButtonsTitles($iProfileId, $sConnectionsObject = 'sys_profiles_friends')
+    {
+        if (!isLogged())
+            return array();
+
+        if (!($oConn = BxDolConnection::getObjectInstance($sConnectionsObject)))
+            return array();
+
+        $CNF = $this->_oConfig->CNF;
+
+        if ($oConn->isConnectedNotMutual(bx_get_logged_profile_id(), $iProfileId)) {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend_sent']),
+                'remove' => _t($CNF['T']['menu_item_title_unfriend_cancel_request']),
+            );
+        } elseif ($oConn->isConnectedNotMutual($iProfileId, bx_get_logged_profile_id())) {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend_confirm']),
+                'remove' => _t($CNF['T']['menu_item_title_unfriend_reject_request']),
+            );
+        } elseif ($oConn->isConnected($iProfileId, bx_get_logged_profile_id(), true)) {
+            return array(
+                'add' => '',
+                'remove' => _t($CNF['T']['menu_item_title_unfriend']),
+            );
+        } else {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend']),
+                'remove' => '',
+            );
+        }
+    }
+
+
+    // ====== PERMISSION METHODS
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileView
+     */ 
+    public function serviceCheckAllowedProfileView($iContentId)
+    {        
+        return $this->serviceCheckAllowedWithContent('View', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileContact
+     */ 
+    public function serviceCheckAllowedProfileContact($iContentId)
+    {        
+        return $this->serviceCheckAllowedWithContent('Contact', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedPostInProfile
+     */
+    public function serviceCheckAllowedPostInProfile($iContentId)
+    {
+        return $this->serviceCheckAllowedWithContent('Post', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckSpacePrivacy
+     */ 
+    public function serviceCheckSpacePrivacy($iContentId)
+    {
+        return $this->serviceCheckAllowedProfileView($iContentId);
+    }
+
+    /**
+     * Check if the profile can be viewed.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedProfileView service method or 
+     * BxDolProfile::checkAllowedProfileView method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($oProfile && $oProfile->id() == $iProfileId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return parent::serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * Check if posting (comment, post in Timeline) is available.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedPostInProfile service method or 
+     * BxDolProfile::checkAllowedPostInProfile method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedPostForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        // check is view allowed
+        if(($mixedResult = $this->serviceCheckAllowedViewForProfile($aDataEntry, $isPerformAction, $iProfileId)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        // moderator and owner always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if(($oProfile && $oProfile->id() == $iProfileId) || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check privacy
+        if(!empty($CNF['OBJECT_PRIVACY_POST'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST']);
+            if($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_post', 0, 0, array('module' => $this->getName(), 'content_info' => $aDataEntry, 'profile_id' => $iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * Check if profile can be contacted.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedProfileContact service method or 
+     * BxDolProfile::checkAllowedProfileContact method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedContactForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $mixedResult = $this->_serviceCheckAllowedContactForProfile($aDataEntry, $isPerformAction, $iProfileId);
+
+        // call alert to allow custom checks
+        bx_alert('system', 'check_allowed_contact', 0, 0, array(
+            'module' => $this->getName(), 
+            'content_info' => $aDataEntry, 
+            'profile_id' => $iProfileId, 
+            'override_result' => &$mixedResult
+        ));
+
+        return $mixedResult;
+    }
+
+    public function serviceSetViewProfileCover($oPage, $aProfileInfo)
+    {
+        $this->_oTemplate->setCover($oPage,$aProfileInfo);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedAdd ($isPerformAction = false)
+    {
+        $oAccount = BxDolAccount::getInstance();
+        if (!$oAccount || ($this->serviceActAsProfile() && $oAccount->isProfilesLimitReached()))
+            return _t('_sys_txt_access_denied');
+        return parent::checkAllowedAdd ($isPerformAction);
+    }   
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewProfileImage ($aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // check privacy
+        if (empty($CNF['OBJECT_PRIVACY_VIEW'])) 
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']]) && !$oPrivacy->isPartiallyVisible($aDataEntry[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            return _t('_sys_access_denied_to_private_content');
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewCoverImage ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedViewProfileImage($aDataEntry);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedPost ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedPostForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedContact ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedContactForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // owner (checked by account instead of author!) always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        if ($oProfile->id() == $this->_iProfileId || $oProfile->getAccountId() == $this->_iAccountId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * Check if user can change cover image
+     */
+    public function checkAllowedChangeCover ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // owner (checked by account instead of author!) always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        if ($oProfile->id() == $this->_iProfileId || $oProfile->getAccountId() == $this->_iAccountId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // owner (checked by account instead of author!) always have access
+        $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->_aModule['name'], $isPerformAction);
+        if (!$oProfile)
+            return _t('_sys_txt_error_occured');
+
+        if (($oProfile->id() == $this->_iProfileId || $oProfile->getAccountId() == $this->_iAccountId) && $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewMoreMenu (&$aDataEntry, $isPerformAction = false)
+    {
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE']);
+        if (!$oMenu || !$oMenu->getCode())
+            return _t('_sys_txt_access_denied');
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * Compose differs from Contact. For example, a user should NOT be able to start conversation (compose) 
+     * with himself but he should be able to view conversation page where he is one of the participants.
+     * 
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedCompose(&$aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if($oProfile && $oProfile->id() == $this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        return $this->checkAllowedContact($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFriendAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFriendRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', false, true, true))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', false, true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedRelationAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aDataEntry)))
+            return $sMsg;
+
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_relations', false, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedRelationRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_relations', false, true);
+    }
+
+    public function checkAllowedRelationsView (&$aDataEntry, $isPerformAction = false)
+    {
+        $sResult = _t('_sys_txt_access_denied');
+
+        $sModule = $this->_oConfig->getName();
+        $oRelations = BxDolConnection::getObjectInstance('sys_profiles_relations');
+        if(!$oRelations->isRelationAvailableWithProfile($sModule) && !$oRelations->isRelationAvailableFromProfile($sModule))
+            return $sResult;
+
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return $sResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aDataEntry)))
+            return $sMsg;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_subscriptions', false, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_subscriptions', false, true);
+    }
+
+    public function checkAllowedSubscriptionsView (&$aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sResult = _t('_sys_txt_access_denied');
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return $sResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name']);
+        if(!$oProfile || ($oProfile->id() != $this->_iProfileId && $this->_oDb->getParam($CNF['PARAM_PUBLIC_SBSN']) != 'on' && $this->_oDb->getParam($CNF['PARAM_PUBLIC_SBSD']) != 'on'))
+            return $sResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkMyself($iContentId)
+    {
+		$iLogged = (int)bx_get_logged_profile_id();
+    	if(empty($iLogged))
+    		return false;
+
+    	$oProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->_oConfig->getName());
+    	if(!$oProfile)
+    		return false;
+
+		return $oProfile->id() == $iLogged;
+    }
+
+    protected function _serviceCheckAllowedContactForProfile($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        /*
+         * allow to 'contact myself'. It's needed to allow a user to view conversation page 
+         * where he is one of the participants.
+         */
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if($oProfile && $oProfile->id() == $iProfileId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // moderator always has access
+        if($this->_isModeratorForProfile($isPerformAction, $iProfileId))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check privacy
+        if(!empty($CNF['OBJECT_PRIVACY_CONTACT'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_CONTACT']);
+            if($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    // ====== COMMON METHODS
+    public function alertAfterAdd($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $aParams = array();        
+        if(isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            $aParams['privacy_view'] = $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']];
+
+        bx_alert($sModule, 'added', $iContentId, false, $aParams);
+    }
+
+    public function alertAfterEdit($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $sModule);
+
+        bx_alert($sModule, 'edited', $iContentId);
+        bx_alert('profile', 'edit', $oProfile->id(), 0, array('content' => $iContentId, 'module' => $sModule));
+    }
+
+    public function getProfileByCurrentUrl ()
+    {
+        $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        
+        if ($iProfileId)
+            return  BxDolProfile::getInstance($iProfileId);
+
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if ($iContentId)
+            return BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        
+        return false;
+    }
+    
+    public function getEntryImageData($aContentInfo, $sField = 'FIELD_PICTURE', $aTranscoders = array())
+    {
+        return parent::getEntryImageData($aContentInfo, $sField, $aTranscoders);
+    }
+
+
+    // ====== PROTECTED METHODS
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        if (!$this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile || $oProfile->id() == $this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        return BxDolConnection::getObjectInstance($sObjConnection)->checkAllowedConnect ($this->_iProfileId, $oProfile->id(), $isPerformAction, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    protected function _buildRssParams($sMode, $aArgs)
+    {
+        $aParams = array ();
+        $sMode = bx_process_input($sMode);
+        switch ($sMode) {
+            case 'connections':
+                $aParams = array(
+                    'object' => isset($aArgs[0]) ? $aArgs[0] : '',
+                    'type' => isset($aArgs[1]) ? $aArgs[1] : '',
+                    'profile' => isset($aArgs[2]) ? (int)$aArgs[2] : 0,
+                    'mutual' => isset($aArgs[3]) ? (int)$aArgs[3] : 0,
+                    'profile2' => isset($aArgs[4]) ? (int)$aArgs[4] : 0,
+                );
+                break;
+        }
+
+        return $aParams;
+    }
+
+    protected function _serviceBrowseQuick($aProfiles, $iStart = 0, $iLimit = 4, $aAdditionalParams = array())
+    {
+        // get paginate object
+        $oPaginate = new BxTemplPaginate(array(
+            'on_change_page' => "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}', " . bx_js_string(json_encode($aAdditionalParams)) . ");",
+            'num' => count($aProfiles),
+            'per_page' => $iLimit,
+            'start' => $iStart,
+        ));
+
+        // remove last item from connection array, because we've got one more item for pagination calculations only
+        if (count($aProfiles) > $iLimit)
+            array_pop($aProfiles);
+
+        // get profiles HTML
+        $s = '';
+        foreach ($aProfiles as $mixedProfile) {
+            $bProfile = is_array($mixedProfile);
+
+            $oProfile = BxDolProfile::getInstance($bProfile ? (int)$mixedProfile['id'] : (int)$mixedProfile);
+            if(!$oProfile)
+                continue;
+
+            $aUnitParams = array('template' => array('name' => 'unit', 'size' => 'thumb'));
+            if(BxDolModule::getInstance($oProfile->getModule()) instanceof BxBaseModGroupsModule)
+                $aUnitParams['template']['name'] = 'unit_wo_cover';
+
+            if($bProfile && is_array($mixedProfile['info']))
+                $aUnitParams['template']['vars'] = $mixedProfile['info'];
+
+            $s .= $oProfile->getUnit(0, $aUnitParams);
+        }
+
+        // return profiles + paginate
+        return $s . (!$iStart && $oPaginate->getNum() <= $iLimit ?  '' : $oPaginate->getSimplePaginate());
+    }
+
+    protected function _serviceGetTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aFileInfo = BxDolStorage::getObjectInstance($CNF[$aBuildParams['stg']])->getFile((int)$aEvent['object_id']);
+        if(empty($aFileInfo) || !is_array($aFileInfo))
+            return false;
+            
+        $aEventContent = unserialize($aEvent['content']);
+        if(!is_array($aEventContent) || empty($aEventContent['content']))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEventContent['content']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo['profile_id']);
+        if (!$oProfile->isActive())
+            return false;
+            
+        $sUserName = $oProfile->getDisplayName();
+
+        $sSample = isset($CNF['T'][$aBuildParams['txt_sswa']]) ? $CNF['T'][$aBuildParams['txt_sswa']] : $CNF['T'][$aBuildParams['txt_ss']];
+
+        //--- Title & Description
+        $sTitle = !empty($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TEXT']];
+
+        $sDescription = _t($CNF['T'][$aBuildParams['txt_sau']], $sUserName, _t($sSample));
+
+        return array(
+            'owner_id' => $aEvent['owner_id'],
+            'object_owner_id' => $aContentInfo['profile_id'],
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => $sSample,
+            'sample_wo_article' => $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_action' => isset($CNF['T'][$aBuildParams['txt_sa']]) ? $CNF['T'][$aBuildParams['txt_sa']] : '',
+            'url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'content' => $this->_getContentForTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams, $aContentInfo, $aFileInfo), //a string to display or array to parse default template before displaying.
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => '',
+            'votes' => '',
+            'scores' => '',
+            'reports' => '',
+            'comments' => '',
+            'title' => $sTitle, //may be empty.
+            'description' => $sDescription //may be empty.
+        );
+    }
+
+    protected function _serviceGetTimelineProfileImageAllowedView($aEvent)
+    {
+        $sError = _t('_sys_access_denied_to_private_content');
+
+        if(empty($aEvent['content']) || !is_array($aEvent['content']) || empty($aEvent['content']['id']))
+            return $sError;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['content']['id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return $sError;
+
+        return $this->serviceCheckAllowedViewForProfile($aContentInfo);
+    }
+
+    protected function _getContentForTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams, $aContentInfo, $aFileInfo)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+    	//--- Image(s)
+        $sImage = $this->_oConfig->getImageUrl($aFileInfo['id'], $aBuildParams['trans']);
+        $sImageOrig = $this->_oConfig->getImageUrl($aFileInfo['id'], $aBuildParams['trans_orig']);
+        if(!empty($sImage)) {
+            if(empty($sImageOrig))
+                $sImageOrig = $sImage;
+
+            $a = array('id' => $aFileInfo['id'], 'url' => $sUrl, 'src' => $sImage);
+            if (CHECK_ACTION_RESULT_ALLOWED === $this->checkAllowedView($aContentInfo))
+                $a['src_orig'] = $sImageOrig;
+            $aImages = array($a);
+        }
+
+    	return array(
+            'sample' => isset($CNF['T'][$aBuildParams['txt_sswa']]) ? $CNF['T'][$aBuildParams['txt_sswa']] : $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_wo_article' => $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_action' => isset($CNF['T'][$aBuildParams['txt_sa']]) ? $CNF['T'][$aBuildParams['txt_sa']] : '',
+            'id' => $aContentInfo[$CNF['FIELD_ID']],
+            'url' => $sUrl,
+            'title' =>  '',
+            'text' => '',
+            'images' => $aImages,
+            'videos' => array()
+        );
+    }
+
+    protected function _entityComments($sObject, $iId = 0)
+    {
+        if(!$iId)
+            $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iId)
+            $iId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        if(!$iId)
+            return false;
+
+        $oCmts = BxDolCmts::getObjectInstance($sObject, $iId);
+        if(!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getCommentsBlock(array(), array('in_designbox' => false));
+    }
+
+    protected function _getContent($iContentId = 0, $sFuncGetContent = 'getContentInfoById')
+    {
+        if(!$iContentId && bx_get('id') === false && bx_get('profile_id') !== false) {
+            $oProfile = BxDolProfile::getInstance((int)bx_get('profile_id'));
+            if($oProfile)
+                $iContentId = $oProfile->getContentId();
+        }
+
+        return parent::_getContent($iContentId, $sFuncGetContent);
+    }
+
+    /** 
+     * Returns list of members by mode with limited number of records for React Jot
+     * @param string $sMode
+     * @param int $iStart
+     * @param int $iPerPage
+     * @return mixed
+     */
+    public function serviceGetMembers($sMode = 'active', $iStart = 0, $iPerPage = 10){
+        bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass($sMode);
+
+        $o -> aCurrent['paginate'] = array('perPage' => $iPerPage, 'forceStart' => $iStart);
+        return $o -> getSearchData();
+    }
+}
+
+/** @} */

+ 68 - 0
upgrade/files/11.0.1-11.0.2/files/modules/base/text/classes/BxBaseModTextMenuAttachments.php

@@ -0,0 +1,68 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Create\edit entry attachments menu
+ */
+class BxBaseModTextMenuAttachments extends BxTemplMenu
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+ 
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        $this->addMarkers(array(
+            'js_object' => $this->_oModule->_oConfig->getJsObject('poll')
+        ));
+    }
+
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        $sUploader = '';
+        switch ($a['name']) {
+            case 'photo_simple':
+                $sUploader = 'photos_simple';
+                break;
+            case 'photo_html5':
+                $sUploader = 'photos_html5';
+                break;
+            case 'video_simple':
+                $sUploader = 'videos_simple';
+                break;
+            case 'video_html5':
+                $sUploader = 'videos_html5';
+                break;
+            case 'file_simple':
+                $sUploader = 'files_simple';
+                break;
+            case 'file_html5':
+                $sUploader = 'files_html5';
+                break;
+        }
+
+        if(!empty($sUploader)) {
+            $sUploader = 'js_object_uploader_' . $sUploader;
+            if(!isset($this->_aMarkers[$sUploader]))
+                return false;
+        }
+
+        return true;
+    }
+}
+
+/** @} */

+ 641 - 0
upgrade/files/11.0.1-11.0.2/files/template/css/forms.css

@@ -0,0 +1,641 @@
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:first-child {
+    margin-top:0;
+}
+
+.bx-form-advanced-wrapper .bx-form-section-divider:first-child {
+    border-top:none; 
+    padding-top:0; 
+}
+
+.bx-form-advanced-wrapper .bx-form-section-divider:first-child .bx-form-section-content {
+    padding-top:0; 
+}
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:last-child .bx-form-section-content {
+    padding-bottom:0;
+}
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:first-child .bx-form-row-view-wrapper:first-child {
+    padding-top:0;
+}
+
+.bx-form-section-wrapper {
+    position:relative;
+}
+
+.bx-form-section {
+    padding:0px;
+}
+
+.bx-form-section {
+    margin:0;
+    border: none;
+}
+
+.bx-form-section-header legend {
+    position:relative;
+    text-transform:uppercase;
+}
+
+.bx-form-section-header legend u {
+    cursor:pointer;
+}
+
+.bx-form-section-header span {
+	white-space: nowrap;
+	text-transform: none;
+	font-size: 0.75rem;
+}
+
+.bx-form-section-hidden .bx-form-section-content {
+    display:none;
+}
+
+.bx-form-element-wrapper:first-child {
+    margin-top:0px;
+}
+
+.bx-form-caption {
+    margin-bottom:5px;
+}
+
+.bx-form-required {
+    color:red;
+}
+
+.bx-form-warn {
+    color:red;
+    font-style:italic;
+}
+
+.bx-form-info {
+
+}
+
+/* section toggler */
+
+.bx-form-collapsable .bx-form-section-toggler {
+
+    position:absolute;
+    left: -2px;
+    top: 2px;
+
+    width:20px;
+    height:20px;
+
+    text-decoration:none;
+
+    -moz-transform: rotate(90deg);
+    -webkit-transform: rotate(90deg);
+    -ms-transform: rotate(90deg);
+    -o-transform: rotate(90deg);
+
+    -webkit-transition: all 0.5s ease;
+    -moz-transition: all 0.5s ease;
+    -ms-transition: all 0.5s ease;
+    -o-transition: all 0.5s ease;
+}
+
+.bx-form-collapsed .bx-form-section-toggler {
+    -moz-transform: rotate(0deg);
+    -webkit-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+}
+
+.bx-form-section-toggler .sys-icon {
+    display: inline-block;
+    vertical-align: top;
+    margin: 0 0 0 5px;
+    font-size:0.875rem;
+    line-height:1.2rem;
+}
+
+/* form inputs */
+.bx-form-input-wrapper {
+    position: relative;
+}
+
+.bx-form-input-disabled {}
+
+.bx-form-input-slider,
+.bx-form-input-doublerange,
+.bx-form-input-select_multiple,
+.bx-form-input-select,
+.bx-form-input-radio_set,
+.bx-form-input-checkbox_set,
+.bx-form-input-number,
+.bx-form-input-time,
+.bx-form-input-datepicker,
+.bx-form-input-datetime,
+.bx-form-input-textarea,
+.bx-form-input-wrapper-password,
+.bx-form-input-text {
+
+    height:40px;
+    height:2.5rem;
+    width:100%;
+    
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    -webkit-appearance: none;
+    -moz-appearance: none;
+    appearance: none;
+
+    padding:7px 3px 7px 3px;
+    padding:0.2rem 0.4rem;
+
+    vertical-align:middle;
+
+    border:2px solid #79bd9a;
+    border:0.125rem solid #79bd9a;
+	
+    -webkit-border-radius:0.3rem;
+    -moz-border-radius:0.3rem;	
+    border-radius:0.3rem;
+
+    outline:none;
+
+    background-color: #fff;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; 
+    filter: alpha(opacity=90); 
+    -moz-opacity: 0.9;
+    -khtml-opacity: 0.9;
+    opacity: 0.9;    
+}
+
+.bx-form-input-slider:focus,
+.bx-form-input-doublerange:focus,
+.bx-form-input-number:focus,
+.bx-form-input-time:focus,
+.bx-form-input-datepicker:focus,
+.bx-form-input-datetime:focus,
+.bx-form-input-textarea:focus,
+.bx-form-input-text:focus,
+.bx-form-input-wrapper-password.bx-form-input-focus {
+    border-color:#6caa8a;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 
+    filter: alpha(opacity=100); 
+    -moz-opacity: 1.0;
+    -khtml-opacity: 1.0;
+    opacity: 1.0;
+
+    -webkit-transition: all 0.2s linear;
+    -moz-transition: all 0.2s linear;
+    -o-transition: all 0.2s linear;
+    transition: all 0.2s linear;
+}
+.bx-form-input-wrapper-password {
+    padding: 0 2.125rem 0 0;
+}
+.bx-form-input-wrapper-password > a {
+    position: absolute;
+    z-index: 1000;
+    
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    justify-content: center;
+
+    top: 0;
+    right: 0;    
+    height: 100%;
+
+    margin: 0 0.5rem;
+}
+.bx-form-input-password {
+    position: relative;
+
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    width: 100%;
+    height: 100%;
+
+    margin: 0;
+    padding: 7px 3px 7px 3px;
+    padding: 0.2rem 0.4rem;
+
+    border: 0;
+    background-color: transparent;
+}
+.bx-form-input-password:focus {
+    border-color: transparent;
+    outline: none;
+    box-shadow: none;
+}
+
+.bx-form-input-select_multiple,
+.bx-form-input-radio_set,
+.bx-form-input-checkbox_set {
+    height:auto;
+}
+
+.bx-form-input-textarea {
+    padding:0.5rem;
+    height:150px;
+    height:10rem;
+
+    line-height:normal;
+}
+
+.bx-form-input-datepicker {
+    width:7.5rem;
+}
+
+.bx-form-input-datetime {
+    width:10.2rem;
+}
+
+.bx-form-datepicker-modal {
+    z-index:1001 !important;
+}
+
+.bx-form-input-number,
+.bx-form-input-time
+{
+    width:6rem;
+}
+
+.bx-form-input-wrapper-file .bx-btn {
+	float: none;
+	display: inline-block;
+}
+.bx-form-input-file {
+	position: fixed;
+	top: -1000px;
+
+    border: none;
+    background-color: transparent;
+}
+
+.bx-form-input-wrapper-slider,
+.bx-form-input-wrapper-doublerange {
+    position:relative;
+}
+
+.bx-form-input-wrapper-slider .ui-slider,
+.bx-form-input-wrapper-doublerange .ui-slider {
+    position:absolute;
+    top:0px;
+    right:0px;
+}
+
+.bx-form-input-wrapper-slider .ui-slider .ui-slider-handle,
+.bx-form-input-wrapper-doublerange .ui-slider .ui-slider-handle { 
+    z-index: 1;
+}
+
+/* switcher */
+
+.bx-switcher-cont {
+	position:relative;
+    width:60px;
+	width:3.8rem;
+    height:24px;
+	height:1.5rem;
+	overflow:hidden;
+	cursor:pointer;
+
+    background-color:#fff;
+
+    border:2px solid #79bd9a;
+    border:0.125rem solid #79bd9a;
+    -moz-border-radius: 1rem;
+    -webkit-border-radius: 1rem;
+    border-radius: 1rem;
+}
+.bx-switcher-cont input {
+    position:absolute; 
+    top:-32px;
+    top:-2rem;
+}
+.bx-switcher-canvas {
+	position:relative;
+	width:90px;
+	width:5.7rem;
+
+    -webkit-transition: left 0.2s ease-in-out;
+    -moz-transition: left 0.2s ease-in-out;
+    -ms-transition: left 0.2s ease-in-out;
+    -o-transition: left 0.2s ease-in-out;
+}
+.bx-switcher-cont.on .bx-switcher-canvas {
+	left:3px;
+	left:0.2rem;
+}
+.bx-switcher-cont.off .bx-switcher-canvas {
+	left:-34px;
+	left:-2.1rem;
+}
+.bx-switcher-on,
+.bx-switcher-off,
+.bx-switcher-handler {
+    position:absolute;	
+    top:0rem;
+	line-height:24px;
+	line-height:1.5rem;
+    color:#999;
+}
+.bx-switcher-on {
+    left:0rem;
+    width:34px;
+    width:2.1rem;
+    height:24px;
+    height:1.5rem;
+    margin-left:8px;
+    margin-left:0.5rem;
+}
+.bx-switcher-off {	
+    left:65px;
+    left:4.05rem;
+    width:34px;
+    width:2.1rem;
+    height:24px;
+    height:1.5rem;
+    margin-left:3px;
+    margin-left:0.2rem;
+}
+.bx-switcher-handler {
+    left:36px;
+    left:2.23rem;
+    top:2px;
+    top:0.13rem;
+    width:20px;
+    width:1.25rem;
+	height:20px;
+	height:1.25rem;
+	margin:0rem;
+
+    -moz-border-radius: 1rem;
+    -webkit-border-radius: 1rem;
+    border-radius: 1rem;
+
+    -webkit-transition: background-color 0.2s ease-in-out;
+    -moz-transition: background-color 0.2s ease-in-out;
+    -ms-transition: background-color 0.2s ease-in-out;
+    -o-transition: background-color 0.2s ease-in-out;
+    transition: background-color 0.2s ease-in-out;
+}
+.bx-switcher-cont.on .bx-switcher-handler {
+    background-color:#d0efa2;
+}
+.bx-switcher-cont.off .bx-switcher-handler {
+    background-color:#e7938a;
+}
+.bx-form-element-wrapper-oneline .bx-form-element {
+	display: -webkit-flex;
+    display: flex;
+    -webkit-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+.bx-form-value-oneline,
+.bx-form-caption-oneline {
+    float: none;
+}
+.bx-form-caption-oneline-switcher {
+    line-height:26px;
+}
+.bx-form-input-wrapper-submit .bx-btn {
+    float: none;
+}
+
+.bx-form-input-wrapper-input_set .bx-btn {
+    float:left;
+}
+
+/* extra features */
+
+.bx-form-right-line-aligned {
+    float:right;
+    margin-top: 5px;
+}
+
+/* view mode */
+.bx-form-row-view-wrapper > div {
+	position: relative;
+    display: inline-block;
+    width: 100%;
+
+    vertical-align: bottom;
+    white-space: normal;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.bx-form-row-view-wrapper-textarea > div {
+    vertical-align:top;
+}
+
+.bx-form-row-view-caption {
+    font-weight:bold;
+}
+
+
+/****************************************
+*		Custom reCaptcha Theme
+****************************************/
+.bx-form-input-captcha-custom {
+	background-color: #b71800;
+	padding: 0.5em;
+	border-radius: 1em;
+	position: relative;
+	overflow: auto;
+	max-width: 480px;
+	font: 0.75em/1em Helvetica, Arial, sans-serif;
+	color: #ffffff;
+}
+.bx-form-input-captcha-custom a {
+	color: #ffffff;
+	text-decoration: none;
+}
+.bx-form-input-captcha-custom img,
+.bx-form-input-captcha-custom #recaptcha_image {
+	width: 100% !important;
+	height: auto !important;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+}
+.bx-form-input-captcha-custom .solution {
+	position: relative;
+	float: left;
+	width: 75%;
+}
+.bx-form-input-captcha-custom .solution-cnt {
+	margin-top: 0.5em;
+	padding: 1.2em 1em;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+	background-color: #fbe098;
+	color: #000000;
+	display: block;
+}
+.bx-form-input-captcha-custom .solution-cnt input {
+	display: block;
+	width: calc(90% - 2px);
+	margin: 0.35em auto;
+	-webkit-appereance: none;
+	border: 1px solid #f7c236;
+	font: inherit;
+	font-size: 150%;
+	outline: none;
+}
+.bx-form-input-captcha-custom .solution-cnt input:focus {
+	border-color: #b71800;
+}
+.bx-form-input-captcha-custom .options {
+	position: relative;
+	float: right;
+	width: 25%;
+	
+}
+.bx-form-input-captcha-custom .options-cnt {
+	margin-top: 0.5em;
+	padding-left: 0.5em;
+	-webkit-border-radius: 0.5em;
+	-moz-border-radius: 0.5em;
+	border-radius: 0.5em;
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+}
+.bx-form-input-captcha-custom .options-cnt a {
+	background-color: #c5523d;
+	display: block;
+	padding: 0.5em 1.0em;
+	border-top: 1px solid #000000;
+	border-left: 1px solid #000000;
+	border-right: 1px solid #000000;
+}
+.bx-form-input-captcha-custom .options-cnt a:first-child {
+	border-radius: 0.5em 0.5em 0 0;
+}
+.bx-form-input-captcha-custom .options-cnt a:last-child {
+	border-radius: 0 0 0.5em 0.5em;
+	border-bottom: 1px solid #000000;
+}
+
+
+/****************************************
+* Overwrite 3d Party MiniColors plug-in
+****************************************/
+.bx-form-input-wrapper.bx-form-input-wrapper-text .minicolors-theme-bootstrap .minicolors-swatch {
+	top: 6px;
+	left: 6px;
+
+	width: 25px;
+	width: 1.5625rem;
+	height: 25px;
+	height: 1.5625rem;
+
+	border-radius: 20px;
+	border-radius: 1.25rem;
+}
+
+/* auto-siggestion input */
+
+.bx-form-value .bx-form-input-autotoken {
+    float:left;
+    padding:0px;
+    height:auto;
+	box-shadow: none;
+    min-height: 2.8rem;
+}
+.bx-form-value .bx-form-input-autotoken b {
+    cursor:pointer;
+    display:block;
+    float:left;
+    line-height: 40px;
+    line-height: 2.5rem;
+    padding-right: 1.5em; 
+    margin-left:0.1em;
+    margin-right:0.1em;
+    font-weight:normal;
+	border-radius: 1.25rem;
+}
+.bx-form-value .bx-form-input-autotoken b:hover {
+    opacity:0.7;
+}
+.bx-form-value .bx-form-input-autotoken b:after {
+    font-family: 'sys-user', 'sys-user-fal';
+    content: "\f00d";    
+    position:absolute;    
+    padding:0 0.5em;
+    margin-left: 0.5em;
+    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;
+}
+.bx-form-value .bx-form-input-autotoken .bx-def-thumb-size {
+    width: 2.5rem;
+    height: 2.5rem;
+    line-height: 2.5rem;
+	float:left;	
+}
+.bx-form-value .bx-form-input-autotoken .bx-base-pofile-unit-wo-info {
+    display: inline-block;
+    vertical-align: middle;
+
+    margin-right: 8px;
+    margin-right: 0.5rem;
+}
+.bx-form-value .bx-form-input-autotoken-labels b {
+    padding-left: 1em;
+}
+.bx-form-value .bx-form-input-autotoken-labels-show-more,
+.bx-form-value .bx-form-input-autotoken-labels-sel b {
+    display:inline-block;
+    line-height:1.5rem;
+    font-size:85%;    
+    padding-right:1em;
+    margin-top:0.625rem;
+    font-style:normal;
+}
+.bx-form-value .bx-form-input-autotoken-labels-show-more {
+    margin-left:0.1em;
+    margin-right:0.1em;
+}
+.bx-form-value .bx-form-input-autotoken-labels-sel b:after {
+    display:none;
+}
+.bx-form-input-autotoken-labels-more {
+    display:none;
+}
+.select2-container {
+    width:100% !important;
+}

+ 200 - 0
upgrade/files/11.0.1-11.0.2/files/template/css/media-phone.css

@@ -0,0 +1,200 @@
+
+html.bx-media-phone {
+    font-size:85%;
+}
+
+.bx-media-phone .bx-def-media-phone-hide {
+    display:none !important;
+}
+.bx-media-phone .bx-def-media-phone-show-block {
+    display:block;
+}
+
+
+.bx-media-phone .bx-def-constraint-all *,
+.bx-media-phone .bx-def-constraint-list ol, .bx-def-constraint-list ul,
+.bx-media-phone .bx-def-constraint-pre pre,
+.bx-media-phone .bx-def-constraint-object object,
+.bx-media-phone .bx-def-constraint-iframe iframe,
+.bx-media-phone .bx-def-constraint-img img {
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    max-width:100% !important;
+    overflow:hidden !important;
+}
+.bx-media-phone .bx-def-constraint-img img {
+    height:auto !important;
+}
+.bx-media-phone .bx-def-constraint-pre pre {
+    overflow:scroll !important;
+}
+
+
+/* default.css */ 
+
+.bx-media-phone .bx-def-page-width {
+    width:auto;
+}
+
+.bx-media-phone .bx-page-wrapper.bx-def-padding-sec-leftright {
+	padding-left: 0px;
+	padding-right: 0px;
+}
+
+
+/* general.css */
+
+.bx-media-phone #bx-logo-container {
+    margin:0 6rem 0 6rem;
+}
+.bx-media-phone #bx-footer-wrapper {
+	height: auto;
+}
+
+/* common.css */
+
+.bx-media-phone .bx-popup-responsive.bx-popup-trans-wrapper:not(.bx-popup-image-wrapper),
+.bx-media-phone .bx-popup-responsive:not(.bx-popup-image-wrapper) .bx-popup-trans,
+.bx-media-phone .bx-popup-responsive:not(.bx-popup-image-wrapper):not(.bx-popup-chain).bx-popup-applied,
+.bx-media-phone .bx-popup-responsive .bx-popup-box {
+    position:fixed !important;
+    top:0 !important;
+    left:0 !important;
+    bottom:0 !important;
+    right:0 !important;
+    width:100%;
+    width:100vw;
+    height:100%;
+    height:100vh;
+    overflow:auto;
+}
+
+.bx-media-phone .bx-popup-responsive,
+.bx-media-phone .bx-popup-responsive .bx-popup-trans > div,
+.bx-media-phone .bx-popup-responsive .bx-popup-box > div {
+    border-radius:0;
+    -moz-border-radius:0;
+    -webkit-border-radius:0;
+}
+
+.bx-media-phone .bx-popup-responsive.bx-popup-image-wrapper:not(.foo) .bx-popup-border,
+.bx-media-phone .bx-popup-responsive.bx-popup-trans-wrapper:not(.foo) .bx-popup-border,
+.bx-media-phone .bx-popup-responsive.bx-popup-box-wrapper:not(.foo) .bx-popup-border {
+    border:none;
+    border-radius:0;
+    -moz-border-radius:0;
+    -webkit-border-radius:0;
+}
+
+.bx-media-phone .bx-popup-responsive.bx-popup-image-wrapper .bx-popup,
+.bx-media-phone .bx-popup-responsive.bx-popup-trans-wrapper .bx-popup,
+.bx-media-phone .bx-popup-responsive.bx-popup-box-wrapper .bx-popup {
+	-moz-box-shadow:none;
+	-webkit-box-shadow:none;
+    box-shadow:none;
+}
+
+.bx-media-phone .bx-popup-width {
+    width:100%;
+}
+
+.bx-media-phone .bx-popup-responsive#bx-popup-alert,
+.bx-media-phone .bx-popup-responsive#bx-popup-confirm,
+.bx-media-phone .bx-popup-responsive#bx-popup-prompt {
+    max-width: none;
+}
+
+/* page layouts */
+
+.bx-media-phone .bx-layout-col {
+    float:none !important;
+    width:auto !important;
+}
+
+.bx-media-phone .bx-layout-cell {
+    padding:0 !important;
+}
+
+/* menu.css */
+
+.bx-media-phone ul.bx-menu-toolbar li a span.bx-menu-toolbar-item-title,
+.bx-media-phone #bx-menu-toolbar-2-container ul.bx-menu-toolbar li a span.bx-menu-toolbar-item-title {
+    display:none;
+}
+
+/* loading indicator */
+.bx-media-phone .bx-btn .bx-loading-ajax-btn {
+	margin-top: 2px;
+	margin-bottom: 2px;
+}
+.bx-media-phone .bx-btn.bx-btn-small .bx-loading-ajax-btn {
+    margin-top: 0px;
+    margin-bottom: 0px;
+}
+
+/* grid.css */
+
+html.bx-media-phone .bx-grid-table-wrapper-responsive .bx-grid-table-wrapper {
+    overflow-x:scroll;
+}
+html.bx-media-phone .bx-grid-table-wrapper-responsive .bx-grid-table {
+    min-width:720px;
+}
+html.bx-media-phone .bx-grid-table-wrapper-responsive.bx-grid-table-wrapper2:before,
+html.bx-media-phone .bx-grid-table-wrapper-responsive.bx-grid-table-wrapper2:after {
+    display:block;
+}
+
+
+/* score.css */
+.bx-media-phone div.bx-score-by-list div.bx-score-by-list-cnt {
+	max-height: none;
+}
+
+/* general.css Post Form */
+.bx-media-phone .sys-cpf-submenu .sys-cpf-submenu-item > div {
+    display: block;
+    text-align: center;
+}
+
+
+/* modules/base/profile|main.css - Profile Cover */
+.bx-media-phone .bx-base-pofile-cover .bx-base-pofile-cover-info {
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.bx-media-phone .bx-base-pofile-cover .bx-base-pofile-cover-info .bx-base-pofile-cover-user {
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: center;
+    align-items: center;
+    width: 100%;
+    margin-left: 0;
+    margin-right: 0;
+    padding-left: 0;
+}
+.bx-media-phone .bx-base-pofile-cover .bx-base-pofile-cover-info .bx-base-pofile-cover-user h1.bx-def-unit-info {
+    padding-left: 0;
+}
+.bx-media-phone .bx-base-pofile-cover .bx-base-pofile-cover-info .bx-base-pofile-cover-actions {
+    -webkit-flex-grow: 2;
+    flex-grow: 2;
+
+    width: 100%;
+    max-width: none;
+    margin-right: 0;
+    padding-right: 0;    
+}
+.bx-media-phone .bx-base-pofile-cover .bx-base-pofile-cover-info .bx-base-pofile-cover-actions ul.bx-menu-hor.bx-menu-more-auto {
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+
+.bx-media-phone .bx-base-pofile-cover .bx-base-pofile-cover-info .bx-base-pofile-cover-actions .bx-btn u {
+    display: none;
+}

+ 383 - 0
upgrade/files/11.0.1-11.0.2/files/template/css/vote.css

@@ -0,0 +1,383 @@
+/*--- Voted By ---*/
+div.bx-vote-by-list {}
+div.bx-vote-by-list-cnt {
+    max-height: 200px;
+    max-height: 12.5rem;
+    min-width: 220px;
+    min-width: 13.75rem;
+    overflow: auto;
+}
+div.bx-vote-by-list div.bx-base-pofile-unit {
+    float: none;
+    margin-right: 0px;
+    margin-bottom: 0px;
+}
+
+/*--- Vote Element Styles ---*/
+div.bx-vote div.bx-vote-element-holder,
+div.bx-vote div.bx-vote-counter-holder {
+	position: relative;
+	float: left;
+}
+
+
+/*----------------------------------*/
+/*--- Likes based Element Styles ---*/
+/*----------------------------------*/
+.bx-vote-likes .bx-vote-element-holder,
+.bx-vote-likes .bx-vote-counter-holder {
+	line-height: 20px;
+	line-height: 1.25rem;
+}
+
+.bx-vote-likes-button .bx-vote-element-holder,
+.bx-vote-likes-button .bx-vote-counter-holder {
+	line-height: 36px;
+	line-height: 2.2rem;
+}
+
+.bx-vote-likes-button-small .bx-vote-element-holder,
+.bx-vote-likes-button-small .bx-vote-counter-holder {
+	line-height: 24px;
+	line-height: 1.6rem;
+}
+
+.bx-vote-likes .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled,
+.bx-vote-likes .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled .sys-icon {
+	color: #ddd;
+}
+
+.bx-vote-likes-button .bx-vote-counter-holder,
+.bx-vote-likes-button-small .bx-vote-counter-holder {
+	font-size: 1.5rem;
+}
+
+.bx-vote-likes .bx-vote-counter-holder.bx-btn-border {
+    border-width: 0px;
+}
+
+
+/*--------------------------------------*/
+/*--- Reactions based Element Styles ---*/
+/*--------------------------------------*/
+.bx-vote-reactions .bx-vote-element-holder,
+.bx-vote-reactions .bx-vote-counter-holder {
+	line-height: 20px;
+	line-height: 1.25rem;
+}
+
+.bx-vote-reactions-button .bx-vote-element-holder,
+.bx-vote-reactions-button .bx-vote-counter-holder {
+	line-height: 36px;
+	line-height: 2.2rem;
+}
+
+.bx-vote-reactions-button-small .bx-vote-element-holder,
+.bx-vote-reactions-button-small .bx-vote-counter-holder {
+	line-height: 24px;
+	line-height: 1.6rem;
+}
+
+.bx-vote-reactions .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled,
+.bx-vote-reactions .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled .sys-icon {
+	color: #ddd;
+}
+
+.bx-vote-reactions-button .bx-vote-counter-holder,
+.bx-vote-reactions-button-small .bx-vote-counter-holder {
+	font-size: 1.5rem;
+}
+
+.bx-vote-reactions .bx-vote-element-holder a.bx-vote-do-vote {
+    position: relative;
+    display: inline-block;
+}
+
+.bx-vote-reactions .bx-vote-counter-holder.bx-btn-border {
+    border-width: 0px;
+}
+
+.bx-vote-counter-wrapper-reactions .bx-vote-counter {
+    display: inline-block;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound {
+    position: relative;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound .bx-vote-counter {
+    display: block;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-divided a.bx-vote-counter {
+    margin-right: 0.5rem;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound a.bx-vote-counter.total-count,
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound span.bx-vote-counter.total-count {
+    margin-left: 0.5rem;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound span.bx-vote-counter:not(.total-count) {
+    width: 2.0rem;
+    height: 2.0rem;
+    box-sizing: border-box;
+
+    margin-left: -0.5rem;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound div.bx-vote-counter-wrapper-cnt {
+    margin-left: 0.5rem;
+}
+.bx-media-phone .bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound span.bx-vote-counter:not(.total-count),
+.bx-media-tablet .bx-layout-column-thin .bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound span.bx-vote-counter:not(.total-count),
+.bx-media-tablet .bx-layout-column-half .bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound span.bx-vote-counter:not(.total-count) {
+    margin-left: -1.0rem;
+}
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound span.bx-vote-counter:not(.total-count) span.bx-vote-counter-text {
+    display: none;
+}
+.bx-vote-counter-wrapper-reactions.bx-vc-hidden,
+.bx-vote-counter-wrapper-reactions .bx-vote-counter.bx-vc-hidden {
+    display: none;
+}
+.bx-menu-object-sys_vote_reactions_do li .sys-icon {
+    font-size: 1.5rem;
+}
+
+/*--- Counter Styles ---*/
+.bx-vote-counter .bx-vote-counter-label.bx-def-valign-center .bx-vote-counter-text {
+    line-height: normal;
+}
+
+.bx-vote-counter.bx-vote-counter-likes span.bx-vote-counter-icon,
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound .bx-vote-counter.bx-vote-counter-reactions span.bx-vote-counter-icon {
+    position: relative;
+    box-sizing: border-box;
+
+    width: 2.0rem;
+    height: 2.0rem;
+
+    background-color: rgba(232, 232, 232, 1.0);
+    border: 1px solid #ffffff;
+    border-radius: 1rem;
+
+    line-height: normal;
+    font-size: 1.0rem;
+}
+.bx-vote-counter.bx-vote-counter-likes span.bx-vote-counter-icon:not(.bx-def-align-center),
+.bx-vote-counter-wrapper-reactions.bx-vote-counter-wrapper-reactions-compound .bx-vote-counter.bx-vote-counter-reactions span.bx-vote-counter-icon:not(.bx-def-align-center) {
+    display: inline-block;
+
+    line-height: 1.9rem;
+    text-align: center;
+}
+.bx-vote-counter span.bx-vote-counter-icon .sys-icon {
+    vertical-align: top;
+}
+
+
+/*--- Voted By ---*/
+div.bx-vote-by-list {}
+div.bx-vote-by-list-cnt {
+    max-height: 200px;
+    max-height: 12.5rem;
+    min-width: 220px;
+    min-width: 13.75rem;
+    overflow: auto;
+}
+div.bx-vote-by-list div.bx-base-pofile-unit {
+    float: none;
+    margin-right: 0px;
+    margin-bottom: 0px;
+}
+.bx-vote-bls-header {
+    display: -webkit-flex;
+    display: flex;
+
+    margin: -1.25rem -1.25rem 0 -1.25rem;
+    
+    border-bottom-color: #e6f0fa;
+    border-bottom-width: 2px;
+}
+.bx-vote-bls-content {
+    margin-left: -0.5rem;
+    margin-right: -0.5rem;
+}
+.bx-vote-bls-title,
+.bx-vote-bls-submenu {
+    -webkit-flex: 0 1 auto; 
+    -ms-flex:  0 1 auto; 
+    flex:  0 1 auto;
+    line-height: 3.125rem;
+}
+.bx-vote-bls-title,
+.bx-vote-bls-submenu-cnt,
+.bx-vote-bls-filler {
+    height: 3.125rem;
+}
+.bx-vote-bls-title,
+.bx-vote-bls-filler {
+    -webkit-flex-grow: 3;
+    flex-grow: 3;
+
+    border-bottom: 2px solid rgba(232, 232, 232, 1.0);
+}
+.bx-vote-bls-title {
+    display: none;
+
+    white-space: nowrap;
+    font-size: 1rem;
+    font-weight: 400;
+}
+.bx-vote-bls-submenu {
+    overflow-x: auto;
+    overflow-y: hidden;
+
+    -webkit-overflow-scrolling: touch;
+}
+.bx-vote-bls-submenu-cnt {
+    box-sizing: border-box;
+    padding: 0 0.5rem;
+    border-bottom: 2px solid rgba(232, 232, 232, 1.0);
+}
+.bx-vote-bls-submenu::-webkit-scrollbar {
+    width: 0.5rem;
+}
+.bx-vote-bls-submenu::-webkit-scrollbar-track {
+    -webkit-border-radius: 0.5rem;
+    border-radius: 0.5rem;
+
+    background-color: rgba(0, 0, 0, 0.1); 
+}
+.bx-vote-bls-submenu::-webkit-scrollbar-thumb {
+    -webkit-border-radius: 0.5rem;
+    border-radius: 0.5rem;
+
+    background: rgba(255, 255, 255, 0.9); 
+    -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5);
+}
+.bx-vote-bls-submenu::-webkit-scrollbar-thumb:window-inactive {
+    background: rgba(255, 255, 255, 0.4); 
+}
+.bx-vote-bls-submenu-cnt ul {
+    display: -webkit-flex;
+    display: flex;
+}
+.bx-vote-bls-submenu-cnt ul li {
+    -webkit-flex: 0 1 auto; 
+    -ms-flex:  0 1 auto; 
+    flex:  0 1 auto;
+    box-sizing: border-box;
+    height: 3.125rem;
+
+    border-bottom: 2px solid rgba(232, 232, 232, 1.0);
+    border-radius: 0;
+
+    line-height: 3.125rem;
+    white-space: nowrap;
+}
+.bx-vote-bls-submenu-cnt ul li div {
+    padding: 0 0.625rem;
+}
+.bx-vote-bls-submenu-cnt ul li.bx-menu-inter-act {
+    border-bottom-color: #364f6b;
+}
+.bx-vote-bls-submenu-cnt ul li.bx-menu-inter-act > div {
+    font-weight: bold;
+}
+.bx-vote-bls-content .bx-vote-by-list:not(.bx-vote-bl-summary) {
+    display: none;
+}
+.bx-vote-bls-content .bx-vote-by-items {
+    padding-left: 0.5rem;
+    padding-right: 0.5rem;
+}
+.bx-vote-bls-content .bx-vote-by-item {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.bx-vote-bls-content .bx-vote-by-item:first-child {
+    padding-top: 1rem;
+}
+.bx-vote-bls-content .bx-vote-by-item:last-child {
+    padding-bottom: 0;
+}
+
+.bx-vote-bls-content .bx-vote-by-item:before,
+.bx-vote-bls-content .bx-vote-by-item:after {
+    display: none;
+}
+
+
+/*----------------------------------*/
+/*--- Stars based Element Styles ---*/
+/*----------------------------------*/
+.bx-vote-stars,
+.bx-vote-stars .bx-vote-do,
+.bx-vote-stars .bx-vote-element-holder,
+.bx-vote-stars .bx-vote-counter-holder {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+
+    line-height: 16px;
+    line-height: 1rem;
+    font-size: 16px;
+    font-size: 1rem;
+}
+
+.bx-vote-stars .bx-vote-counter-holder {
+    border-width: 0px;
+}
+
+
+/*--- Stars Styles ---*/
+div.bx-vote-element {
+    position: relative;
+    display: inline-block;
+}
+div.bx-vote-underlay {}
+div.bx-vote-slider {
+    position: absolute;
+    z-index: 1;
+    top: 0px;
+    left: 0px;
+    white-space: nowrap;
+    overflow: hidden;
+}
+.bx-vote-stars .bx-vote-element-holder a.bx-vote-button,
+.bx-vote-stars .bx-vote-element-holder i.bx-vote-star,
+.bx-vote-stars .bx-vote-element-holder span.bx-vote-star {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+    height: 16px;
+    width: 16px;
+    margin: 0;
+    padding: 0;
+    text-align: center;
+}
+div.bx-vote-underlay i.bx-vote-star,
+div.bx-vote-underlay span.bx-vote-star {
+    color: rgba(255, 204, 102, 1);
+}
+div.bx-vote-slider i.bx-vote-star,
+div.bx-vote-slider span.bx-vote-star,
+div.bx-vote-buttons a:hover i.bx-vote-star,
+div.bx-vote-buttons a:hover span.bx-vote-star {
+    color: rgba(255, 100, 0, 1);
+}
+
+div.bx-vote-buttons {
+    position: absolute;
+    z-index: 2;
+    top: 0px;
+    left: 0px;
+}
+div.bx-vote-buttons a:hover {
+	text-decoration: none;
+}
+div.bx-vote-buttons i.bx-vote-star,
+div.bx-vote-buttons span.bx-vote-star {
+	color: transparent;
+}

+ 414 - 0
upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseConfig.php

@@ -0,0 +1,414 @@
+<?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
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+class BxBaseConfig extends BxDol implements iBxDolSingleton
+{
+    protected $_aConfig = array (
+        'bAllowUnicodeInPreg' => false, ///< allow unicode in regular expressions
+        'aLessConfig' => array (
+            'bx-page-width' => '1000px',
+
+            'bx-margin' => '16px',
+            'bx-margin-sec' => '8px',
+            'bx-margin-thd' => '4px',
+
+            'bx-padding' => '16px',
+            'bx-padding-sec' => '8px',
+            'bx-padding-thd' => '4px',
+
+            'bx-font-family' => 'Helvetica, Arial, sans-serif',
+
+            'bx-size-avatar-big' => '192px',
+            'bx-size-avatar' => '96px',
+            'bx-size-thumb' => '48px',
+            'bx-size-icon' => '32px',
+
+            'bx-size-gallery-img-width' => '300px',
+            'bx-size-gallery-img-height' => '200px',
+
+            'bx-color-page' => '#fff',
+            'bx-color-block' => '#fff',
+            'bx-color-box' => 'rgba(242, 242, 242, 1.0)',
+            'bx-color-box-hover' => 'rgba(242, 242, 242, 0.8)',
+            'bx-color-label' => '#e8e8e8',
+            'bx-color-label-menu' => '#999999',
+            'bx-color-sec' => '#f2f2f2',
+            'bx-color-hl' => 'rgba(196, 248, 156, 0.2)',
+            'bx-color-active' => 'rgba(196, 248, 156, 0.4)',
+            'bx-color-disabled' => 'rgba(221, 221, 221, 1.0)',
+
+            'bx-border-width' => '1px',
+            'bx-border-type' => 'solid',
+            'bx-border-color' => '#d0d0d0',
+            'bx-border-color-hr' => 'rgba(232, 232, 232, 0.8)',
+
+            'bx-font-size-default' => '16px',
+            'bx-font-size-small' => '12px',
+            'bx-font-size-middle' => '14px',
+            'bx-font-size-large' => '20px',
+            'bx-font-size-h1' => '32px',
+            'bx-font-size-h2' => '28px',
+            'bx-font-size-h3' => '24px',
+
+            'bx-font-color-default' => '#333',
+            'bx-font-color-grayed' => '#999',
+            'bx-font-color-contrasted' => '#fff',
+
+            'bx-round-corners-radius' => '3px',
+            'bx-round-corners-radius-label' => '4px',
+            'bx-round-corners-radius-label-menu' => '12px',
+        ),
+    );
+
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxTemplConfig();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public function __get($sName)
+    {
+        if (array_key_exists($sName, $this->_aConfig))
+            return $this->_aConfig[$sName];
+
+        trigger_error('Undefined property "' . $sName . '" in ' . get_class($this), E_USER_ERROR);
+
+        return null;
+    }
+
+    public function __isset($sName)
+    {
+        return isset($this->_aConfig[$sName]);
+    }
+
+    protected function setPageWidth($sParamName)
+    {
+        if(!class_exists('BxDolDb') || !BxDolDb::getInstance() || empty($sParamName)) 
+            return;
+
+        $mixedWidth = getParam($sParamName);
+        if(is_numeric($mixedWidth))
+            $mixedWidth .= 'px';
+
+        $this->_aConfig['aLessConfig']['bx-page-width'] = $mixedWidth;
+    }
+
+    protected function _setValue($sKey, $sDefault = '')
+    {
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setSize($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = '0px';
+
+        $sPattern = "/([0-9\.]+\s*((px)|(rem)|(vh))){1}/";
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !preg_match($sPattern, $sValue))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setSizeDivided($sValue)
+    {
+        $aEmpty = array(0, 0, 0, 0);
+
+        $aValues = explode(' ', $sValue);
+        if(empty($aValues) || !is_array($aValues))
+            return $aEmpty;
+
+        $aResult = $aEmpty;
+        switch(count($aValues)) {
+            case 1:
+                $aResult = array($aValues[0], $aValues[0], $aValues[0], $aValues[0]);
+                break;
+
+            case 2:
+                $aResult = array($aValues[0], $aValues[1], $aValues[0], $aValues[1]);
+                break;
+
+            case 3:
+                $aResult = array($aValues[0], $aValues[1], $aValues[2], $aValues[1]);
+                break;
+
+            case 4:
+                $aResult = array($aValues[0], $aValues[1], $aValues[2], $aValues[3]);
+                break;
+        }
+
+        return $aResult;
+    }
+
+    protected function _setInnerSizeDivided($sValue, $aReductor = array('px' => 1, 'rem' => 0.0625))
+    {
+        $aEmpty = array(0, 0, 0, 0);
+
+        $aValues = explode(' ', $sValue);
+        if(empty($aValues) || !is_array($aValues))
+            return $aEmpty;
+
+        $aResult = $aEmpty;
+        switch(count($aValues)) {
+            case 1:
+                $sValue0 = $this->_decreaseValue($aValues[0], $aReductor);
+                $aResult = array($sValue0, $sValue0, $sValue0, $sValue0);
+                break;
+
+            case 2:
+                $sValue0 = $this->_decreaseValue($aValues[0], $aReductor);
+                $sValue1 = $this->_decreaseValue($aValues[1], $aReductor);
+                $aResult = array($sValue0, $sValue1, $sValue0, $sValue1);
+                break;
+
+            case 3:
+                $sValue0 = $this->_decreaseValue($aValues[0], $aReductor);
+                $sValue1 = $this->_decreaseValue($aValues[1], $aReductor);
+                $sValue2 = $this->_decreaseValue($aValues[2], $aReductor);
+                $aResult = array($sValue0, $sValue1, $sValue2, $sValue1);
+                break;
+
+            case 4:
+                $sValue0 = $this->_decreaseValue($aValues[0], $aReductor);
+                $sValue1 = $this->_decreaseValue($aValues[1], $aReductor);
+                $sValue2 = $this->_decreaseValue($aValues[2], $aReductor);
+                $sValue3 = $this->_decreaseValue($aValues[3], $aReductor);
+                $aResult = array($sValue0, $sValue1, $sValue2, $sValue3);
+                break;
+        }
+
+        return $aResult;
+    }
+
+    protected function _decreaseValue($sValue, $aReductor = array('px' => 1, 'rem' => 0.0625))
+    {
+        $sPattern = "/(([0-9\.]+)\s*((px)|(rem))){1}/";
+
+        $aMatches = array();
+        if(!preg_match($sPattern, $sValue, $aMatches))
+            return $sValue;
+
+        if(!isset($aMatches[2]) || empty($aMatches[4]))
+            return $sValue;
+
+        if(!isset($aReductor[$aMatches[4]]))
+            return $sValue;
+
+        return ($aMatches[2] - $aReductor[$aMatches[4]]) . $aMatches[4];
+    }
+
+    protected function _getColorFromRgba($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = '51, 51, 51';
+
+        $sPattern = "/rgba?\s*\(\s*(([0-9]{1,3}\s*,?\s*){3})\s*([0-9\.]+)?\s*\)/";
+
+        $aMatch = array();
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !preg_match($sPattern, $sValue, $aMatch) || empty($aMatch[1]))
+            $sValue = $sDefault;
+        else 
+            $sValue = trim($aMatch[1], ', ');
+
+        return $sValue;
+    }
+
+    protected function _setMargin($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = '0px';
+
+        $sPattern = "/([0-9\.]+\s*((px)|(rem))\s*){1,4}/";
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || ($sValue != 'inherit' && !preg_match($sPattern, $sValue)))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setColorRgb($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = 'rgb(51, 51, 51)';
+
+        $sPattern = "/rgb\s*\(\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*,\s*[0-9]{1,3}\s*\)/";
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !preg_match($sPattern, $sValue))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setColorRgba($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = 'rgba(51, 51, 51, 1.0)';
+
+        $sPattern = "/rgba?\s*\(\s*([0-9]{1,3}\s*,?\s*){3}\s*([0-9\.]+)?\s*\)/";
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !preg_match($sPattern, $sValue))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+    
+    protected function _setColorRgbaCustom($sKey, $sOpacity, $sDefault = '')
+    {
+        return "rgba(" . $this->_getColorFromRgba($sKey, $sDefault) . ", " . $sOpacity . ")";
+    }
+
+    protected function _setGradientLeft($sKey, $sDefault = '')
+    {
+        $sValue = $this->_getColorFromRgba($sKey, $sDefault);
+        return "linear-gradient(to right, rgba(" . $sValue . ", 1) 0%, rgba(" . $sValue . ", 0) 100%)";
+    }
+    
+    protected function _setGradientRight($sKey, $sDefault = '')
+    {
+        $sValue = $this->_getColorFromRgba($sKey, $sDefault);
+        return "linear-gradient(to right, rgba(" . $sValue . ", 0) 0%, rgba(" . $sValue . ", 1) 100%)";
+    }
+    
+    /**
+     * Can be removed in on of the next versions.
+     * 
+     * @deprecated since 10.0.0-B2
+     * @see BxBaseConfig::_setGradientLeft
+     */
+    protected function _setGradientMenuPageLeft($sKey, $sDefault = '')
+    {
+        return $this->_setGradientLeft($sKey, $sDefault);
+    }
+
+    /**
+     * Can be removed in on of the next versions.
+     * 
+     * @deprecated since 10.0.0-B2
+     * @see BxBaseConfig::_setGradientRight
+     */
+    protected function _setGradientMenuPageRight($sKey, $sDefault = '')
+    {
+        return $this->_setGradientRight($sKey, $sDefault);
+    }
+
+    protected function _setBgUrl($sKey, $oStorage = null)
+    {
+        if(empty($oStorage))
+            $oStorage = BxDolStorage::getObjectInstance('sys_images_custom');
+
+        $iImageId = (int)getParam($sKey);
+        if(empty($iImageId))
+            return "none";
+
+        $sImageUrl = $oStorage->getFileUrlById($iImageId);
+        if(empty($sImageUrl))
+            return "none";
+
+        return "url('" . $sImageUrl . "')";
+    }
+
+    protected function _setBgRepeat($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = 'repeat';
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !in_array($sValue, array('no-repeat', 'repeat', 'repeat-x', 'repeat-y')))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setBgAttachment($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = 'scroll';
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !in_array($sValue, array('fixed', 'scroll', 'local')))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setBgSize($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = 'auto';
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !in_array($sValue, array('auto', 'cover', 'contain')))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setShadow($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = '0px 1px 3px 0px rgba(0, 0, 0, 0.25)';
+
+        $sPattern = "/((-?[0-9]+\s*px\s*){4}\s*rgba\s*\(\s*([0-9]{1,3}\s*,\s*){3}\s*[0-9\.]+\s*\)\s*,?\s*)+/";
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || $sValue == 'none')
+            $sValue = 'none';
+        else if(!preg_match($sPattern, $sValue))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setShadowFont($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = '0px 1px 3px rgba(0, 0, 0, 0.25)';
+
+        $sPattern = "/(-?[0-9]+\s*px\s*){3}\s*rgba\s*\(\s*([0-9]{1,3}\s*,\s*){3}\s*[0-9\.]+\s*\)/";
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || $sValue == 'none')
+            $sValue = 'none';
+        else if(!preg_match($sPattern, $sValue))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+
+    protected function _setAlign($sKey, $sDefault = '')
+    {
+        if(empty($sDefault))
+            $sDefault = 'left';
+
+        $sValue = trim(getParam($sKey));
+        if(!$this->_isModule || empty($sValue) || !in_array($sValue, array('left', 'center', 'right')))
+            $sValue = $sDefault;
+
+        return $sValue;
+    }
+}
+
+/** @} */

+ 655 - 0
upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseFunctions.php

@@ -0,0 +1,655 @@
+<?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 BxBaseFunctions extends BxDolFactory implements iBxDolSingleton
+{
+    protected $_oTemplate;
+    protected $_sDesignBoxIcon = 'chevron';
+
+    protected function __construct($oTemplate)
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->_oTemplate = $oTemplate ? $oTemplate : BxDolTemplate::getInstance();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    public static function getInstanceWithTemplate($oTemplate)
+    {
+        if(!isset($GLOBALS['bxDolClasses']['BxTemplFunctions']))
+            $GLOBALS['bxDolClasses']['BxTemplFunctions'] = new BxTemplFunctions($oTemplate);
+
+        return $GLOBALS['bxDolClasses']['BxTemplFunctions'];
+    }
+    
+    public static function getInstance()
+    {
+        return self::getInstanceWithTemplate(null);
+    }
+
+    function TemplPageAddComponent($sKey)
+    {
+        switch( $sKey ) {
+            case 'something':
+                return false; // return here additional components
+            default:
+                return false; // if you have not such component, return false!
+        }
+    }
+
+    function msgBox($sText, $iTimer = 0, $sOnClose = "")
+    {
+        $iId = time() . mt_rand(1, 1000);
+
+        if($iTimer > 0)
+            BxDolTemplate::getInstance()->addJs(array('jquery.anim.js'));
+
+        return $this->_oTemplate->parseHtmlByName('messageBox.html', array(
+            'id' => $iId,
+            'msgText' => $sText,
+            'bx_if:timer' => array(
+                'condition' => $iTimer > 0,
+                'content' => array(
+                    'id' => $iId,
+                    'time' => 1000 * $iTimer,
+                    'on_close' => $sOnClose,
+                )
+            )
+        ));
+    }
+
+    /**
+     * Get standard popup box with title.
+     *
+     * @param  string $sName    - unique name
+     * @param  string $sTitle   - translated title
+     * @param  string $sContent - content of the box
+     * @return HTML   string
+     */
+    function popupBox($sName, $sTitle, $sContent, $isHiddenByDefault = false)
+    {
+        $iId = !empty($sName) ? $sName : time();
+
+        return $this->_oTemplate->parseHtmlByName('popup_box.html', array(
+            'id' => $iId,
+            'wrapper_style' => $isHiddenByDefault ? 'display:none;' : '',
+            'title' => $sTitle,
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * Get popup box without title.
+     *
+     * @param  string $sName    - unique name
+     * @param  string $sContent - content of the box
+     * @return HTML   string
+     */
+    function transBox($sName, $sContent, $isHiddenByDefault = false, $isPlaceInCenter = false)
+    {
+    	return $this->simpleBox($sName, $sContent, $isHiddenByDefault, $isPlaceInCenter, 'popup_trans.html');
+    }
+
+    function slideBox($sName, $sContent, $isHiddenByDefault = false)
+    {
+    	return $this->simpleBox($sName, $sContent, $isHiddenByDefault, false, 'popup_slide.html');
+    }
+
+	function inlineBox($sName, $sContent, $isHiddenByDefault = false)
+    {
+        return $this->simpleBox($sName, $sContent, $isHiddenByDefault, false, 'popup_inline.html');
+    }
+
+    protected function simpleBox($sName, $sContent, $isHiddenByDefault, $isPlaceInCenter, $sTemplate) {
+    	$iId = !empty($sName) ? $sName : time();
+
+		$sContent = $this->_oTemplate->parseHtmlByName($sTemplate, array(
+            'id' => $iId,
+            'wrapper_style' => $isHiddenByDefault ? 'display:none;' : '',
+            'content' => $sContent
+        ));
+
+        if($isPlaceInCenter)
+        	$sContent = '<div class="login_ajax_wrap">' . $sContent . '</div>';
+
+        return $sContent;
+    }
+
+    function getTemplateIcon($sName)
+    {
+        $sUrl = $this->_oTemplate->getIconUrl($sName);
+        return !empty($sUrl) ? $sUrl : $this->_oTemplate->getIconUrl('spacer.gif');
+    }
+
+    function getTemplateImage($sName)
+    {
+        $sUrl = $this->_oTemplate->getImageUrl($sName);
+        return !empty($sUrl) ? $sUrl : $this->_oTemplate->getImageUrl('spacer.gif');
+    }
+
+    function sysIcon ($sIcon, $sName, $sUrl = '', $iWidth = 0)
+    {
+        return '<div class="sys_icon">' . ($sUrl ? '<a title="'.$sName.'" href="'.$sUrl.'">' : '') . '<img alt="'.$sName.'" src="'.$sIcon.'" '.($iWidth ? 'width='.$iWidth : '').' />' . ($sUrl ? '</a>' : '') . '</div>';
+    }
+
+    /**
+     * functions for limiting maximal string length
+     */
+    function getStringWithLimitedLength($sString, $iWidth = 45, $isPopupOnOverflow = false, $bReturnString = true)
+    {
+        if (empty($sString) || mb_strlen($sString, 'UTF-8') <= $iWidth)
+            return $bReturnString ? $sString : array($sString);
+
+        $sResult = '';
+        $aWords = mb_split("[\s\r\n]", $sString);
+        $iPosition = 0;
+        $iWidthReal = $iWidth - 3;
+        $iWidthMin = $iWidth - 15;
+        foreach($aWords as $sWord) {
+            $sWord = trim($sWord);
+            $iWord = mb_strlen($sWord, 'UTF-8');
+            if ($iPosition + $iWord > $iWidthReal)
+                break;
+
+            // add word and continue
+            $sResult .= ' ' . $sWord;
+            $iPosition += 1 + $iWord;
+        }
+
+        // last word is too long, cut it
+        if(!$iPosition || $iPosition < $iWidthMin)
+            $sResult .= ' ' . mb_substr($sWord, 0, $iWidthReal - $iPosition - $iWord, 'UTF-8');
+        $sResult = trim($sResult);
+
+        // add tripple dot
+        if(!$isPopupOnOverflow) {
+            $sResult .= '...';
+            return $bReturnString ? $sResult : array($sResult);
+        }
+
+        // add button width popup
+        $sId = 'bx-str-limit-' . rand(1, PHP_INT_MAX);
+        $sPopup = '<img class="bx-str-limit" onclick="$(\'#' . $sId . '\').dolPopup({pointer:{el:$(this), offset:\'10 1\'}})" src="' . $this->getTemplateImage('str-limit.png') . '"/>';
+        $sPopup .= '<div id="' . $sId . '" style="display:none;">' . BxTemplFunctions::getInstance()->transBox('', '<div class="bx-def-padding bx-def-color-bg-block">'.$sString.'</div>') . '</div>';
+
+        return $bReturnString ? $sResult . $sPopup : array($sResult, $sPopup);
+    }
+
+    /**
+     * Display design box with specified title, template, content and menu.
+     * @param $sTitle - design box title, please note that some templates don't use title.
+     * @param $sContent - design box content.
+     * @param $iTemplateNum - number of design box template, use predefined contants only, default is BX_DB_DEF.
+     * @param $mixedMenu - design box menu, it can be:
+     *      - object: instance of BxTemplMenu class
+     *      - string: menu object identifier
+     *      - array: array of menu links to create menu from
+     * @return string
+     *
+     * @see BX_DB_CONTENT_ONLY
+     * @see BX_DB_DEF
+     * @see BX_DB_EMPTY
+     * @see BX_DB_NO_CAPTION
+     * @see BX_DB_PADDING_CONTENT_ONLY
+     * @see BX_DB_PADDING_DEF
+     * @see BX_DB_PADDING_NO_CAPTION
+     */
+    function designBoxContent ($sTitle, $sContent, $iTemplateNum = BX_DB_DEF, $mixedMenu = false)
+    {
+        return $this->_oTemplate->parseHtmlByName('designbox_' . (int)$iTemplateNum . '.html', array(
+            'title' => $sTitle,
+            'designbox_content' => $sContent,
+            'caption_item' => $this->designBoxMenu ($mixedMenu, array (array('menu' => 1))),
+        ));
+    }
+
+    function designBoxMenu ($mixedMenu, $aButtons = array ())
+    {
+        $sCode = '';
+        $aButtonMenu = false;
+        if ($mixedMenu) {
+
+            $sMenu = '';
+
+            if (is_string($mixedMenu)) {
+
+                $oMenu = BxTemplMenu::getObjectInstance($mixedMenu);
+                $sMenu = $oMenu ? $oMenu->getCode () : $mixedMenu;
+
+            } elseif (is_object($mixedMenu) && is_a($mixedMenu, 'BxTemplMenu')) {
+
+                $sMenu = $mixedMenu->getCode();
+
+            } elseif (is_array($mixedMenu)) {
+
+                if (isset($mixedMenu['template']) && isset($mixedMenu['menu_items']))
+                    $aMenu = $mixedMenu;
+                else
+                    $aMenu = array ('template' => 'menu_vertical.html', 'menu_items' => $mixedMenu);
+
+                $oMenu = new BxTemplMenu($aMenu, $this->_oTemplate);
+                $sMenu = $oMenu->getCode ();
+            }
+
+            if ($sMenu) {
+                $bButtonMenu = !empty($aButtons['menu']) && is_array($aButtons['menu']);
+
+                $sId = $bButtonMenu && !empty($aButtons['menu']['id']) ? $aButtons['menu']['id'] : 'bx-menu-db-' . time() . rand(0, PHP_INT_MAX);
+                $sCode .= $this->slideBox($sId, $sMenu, true);
+
+                $aButtonMenu = array('icon-a' => $this->_sDesignBoxIcon, 'onclick' => "bx_menu_slide_inline('#" . $sId . "', this)");
+                if($bButtonMenu) {
+                    $aButtonMenu = array_merge($aButtonMenu, $aButtons['menu']);
+                    $aButtons[] = array('menu' => 1);
+
+                    unset($aButtons['menu']);
+                }
+            }
+
+        }
+
+        if ($aButtons) {
+            $sCode .= '<div class="bx-db-menu"><div class="bx-db-menu-tab bx-db-menu-tab-btn">';
+            foreach ($aButtons as $aButton) {
+                if (isset($aButton['menu']) && $aButton['menu']) {
+                    if (!$aButtonMenu)
+                        continue;
+
+                    $aButton = $aButtonMenu;
+                }
+
+                $aAttrs = array ();
+                if (!empty($aButton['onclick']))
+                    $aAttrs['onclick'] = $aButton['onclick'];
+                if (!empty($aButton['class']))
+                    $aAttrs['class'] = $aButton['class'];
+                $sAttrs = bx_convert_array2attrs ($aAttrs, 'bx-def-margin-sec-left');
+
+                $sCode .= '<a href="javascript:void(0);" ' . $sAttrs . '>';
+                $sCode .= !empty($aButton['icon']) ? '<i class="sys-icon ' . $aButton['icon'] . ' bx-def-font-h2"></i>' : '';
+                $sCode .= !empty($aButton['icon-a']) ? '<i class="sys-icon-a" data-rotate="down" data-icon="' . $aButton['icon-a'] . '"></i>' : '';
+                $sCode .= !empty($aButton['title']) ? $aButton['title'] : '';
+                $sCode .= '</a>';
+            }
+            $sCode .= '</div></div>';
+        }
+
+        return $sCode;
+    }
+
+    /**
+     * Get logo URL.
+     * @return string
+     */
+    function getMainLogoUrl()
+    {
+        $oDesigns = BxDolDesigns::getInstance();
+
+        $iFileId = (int)$oDesigns->getSiteLogo();
+        if(!$iFileId) 
+            return false;
+
+        $aParams = array();
+        if(($iLogoWidth = (int)$oDesigns->getSiteLogoWidth()) > 0)
+            $aParams['x'] = $iLogoWidth;
+
+        if(($iLogoHeight = (int)$oDesigns->getSiteLogoHeight()) > 0)
+            $aParams['y'] = $iLogoHeight;
+
+        if(!empty($aParams))
+            $sFileUrl = BX_DOL_URL_ROOT . bx_append_url_params('image_transcoder.php', array_merge(array('o' => 'sys_custom_images', 'h' => $iFileId), $aParams));
+        else 
+            $sFileUrl = BxDolTranscoder::getObjectInstance('sys_custom_images')->getFileUrl($iFileId);
+
+        return !empty($sFileUrl) ? $sFileUrl : false;
+    }
+
+    /**
+     * Get logo HTML.
+     * @return string
+     */
+    function getMainLogo($aParams = array())
+    {
+        $oDesigns = BxDolDesigns::getInstance();
+
+        $sAlt = $oDesigns->getSiteLogoAlt();
+        if(empty($sAlt))
+            $sAlt = getParam('site_title');
+
+        $sLogo = '<span>' . $sAlt . '</span>';
+
+        $sFileUrl = $this->getMainLogoUrl();
+        if (!empty($sFileUrl)) {
+            $iLogoWidth = (int)$oDesigns->getSiteLogoWidth();
+            $sMaxWidth = $iLogoWidth > 0 ? 'max-width:' . round($iLogoWidth/16, 3) . 'rem;' : '';
+
+            $iLogoHeight = (int)$oDesigns->getSiteLogoHeight();
+            $sMaxHeight = $iLogoHeight > 0 ? 'max-height:' . round($iLogoHeight/16, 3) . 'rem;' : '';
+
+            $sLogo = '<img style="' . $sMaxWidth . $sMaxHeight . '" src="' . $sFileUrl . '" id="bx-logo" alt="' . bx_html_attribute($sAlt, BX_ESCAPE_STR_QUOTE) . '" />';
+        }
+
+        $aAttrs = array(
+            'class' => 'bx-def-font-contrasted',
+            'href' => BX_DOL_URL_ROOT,
+            'title' => bx_html_attribute($sAlt, BX_ESCAPE_STR_QUOTE)
+        );
+        if(!empty($aParams['attrs']) && is_array($aParams['attrs']))
+            $aAttrs = array_merge($aAttrs, $aParams['attrs']);
+
+        return '<a' . bx_convert_array2attrs($aAttrs) . '>' . $sLogo . '</a>';
+    }
+
+    /**
+     * Get HTML code for manifests.
+     * @return HTML string to insert into HEAD section
+     */
+    function getManifests()
+    {
+        $sRet = '';
+        $bLogged = isLogged();
+
+        /*
+         * OneSignal manifest must appear before any other <link rel="manifest" ...> in <head>
+         */
+        $sPushAppId = getParam('sys_push_app_id');
+        if($bLogged && !empty($sPushAppId)) {
+            $aUrlRoot = parse_url(BX_DOL_URL_ROOT);
+
+            $sUrl = BX_DOL_URL_PLUGINS_PUBLIC .  'onesignal/manifest.json.php';
+            $sUrl = bx_append_url_params($sUrl, array('bx_name' => $aUrlRoot['host']));
+
+            $sRet .= '<link rel="manifest" href="' . $sUrl . '" />';
+        }
+
+        return $sRet;
+    }
+
+    /**
+     * Get HTML code for meta icons.
+     * @return HTML string to insert into HEAD section
+     */
+    function getMetaIcons()
+    {
+        $iId = (int)getParam('sys_site_icon');
+        $sImageUrlFav = $sImageUrlFcb = $sImageUrlApl = '';
+
+        if(!empty($iId)) {
+
+            // favicon icon
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance(BX_DOL_TRANSCODER_OBJ_ICON_FAVICON);
+            $sImageUrlFav = $oTranscoder->getFileUrl($iId);
+
+            // facebook icon
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance(BX_DOL_TRANSCODER_OBJ_ICON_FACEBOOK);
+            $sImageUrlFcb = $oTranscoder->getFileUrl($iId);
+
+            // apple touch icon
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance(BX_DOL_TRANSCODER_OBJ_ICON_APPLE);
+            $sImageUrlApl = $oTranscoder->getFileUrl($iId);
+        }
+
+        if(empty($sImageUrlFav))
+            $sImageUrlFav = $this->_oTemplate->getIconUrl('favicon.png');
+
+        if(empty($sImageUrlFcb))
+            $sImageUrlFcb = $this->_oTemplate->getIconUrl('facebook-icon.png');
+
+        if(empty($sImageUrlApl))
+            $sImageUrlApl = $this->_oTemplate->getIconUrl('apple-touch-icon.png');
+
+        $sRet = '';
+        $sRet .= '<link rel="shortcut icon" type="image/x-icon" href="' . $sImageUrlFav . '" />';
+        $sRet .= '<link rel="image_src" sizes="100x100" href="' . $sImageUrlFcb . '" />';
+        $sRet .= '<link rel="apple-touch-icon" sizes="152x152" href="' . $sImageUrlApl . '" />';
+
+        return $sRet;
+    }
+
+    function getInjectionHead() 
+    {
+        return $this->getInjection('getInjHead');
+    }
+
+    function getInjectionHeader() 
+    {
+        return $this->getInjection('getInjHeader');
+    }
+
+    function getInjectionFooter() 
+    {
+        return $this->getInjection('getInjFooter');
+    }
+
+    public function getPopupAlert()
+    {
+        return $this->transBox('bx-popup-alert', $this->_oTemplate->parseHtmlByName('popup_trans_alert_cnt.html', array()), true);
+    }
+
+    public function getPopupConfirm()
+    {
+        return $this->transBox('bx-popup-confirm', $this->_oTemplate->parseHtmlByName('popup_trans_confirm_cnt.html', array()), true);
+    }
+
+    public function getPopupPrompt()
+    {
+        $sInputText = 'bx-popup-prompt-value';
+        $aInputText = array(
+            'type' => 'text',
+            'name' => $sInputText,
+            'attrs' => array(
+                'id' => $sInputText,
+            ),
+            'value' => '',
+            'caption' => ''
+        );
+
+        $oForm = new BxTemplFormView(array(), $this->_oTemplate);
+        return $this->transBox('bx-popup-prompt', $this->_oTemplate->parseHtmlByName('popup_trans_prompt_cnt.html', array(
+            'input' => $oForm->genRow($aInputText)
+        )), true);
+    }
+
+    /**
+     * Output time wrapped in <time> tag in HTML.
+     * Then time is autoformatted using JS upon page load, this is aumatically converted to user's timezone and
+     * updated in realtime in case of short periods of 'from now' time format.
+     *
+     * Short version of this function:
+     * @see bx_time_js
+     *
+     * @param $iUnixTimestamp time as unixtimestamp
+     * @param $sFormatIdentifier output format identifier
+     *     @see BX_FORMAT_DATE
+     *     @see BX_FORMAT_TIME
+     *     @see BX_FORMAT_DATE_TIME
+     * @param $bForceFormat force provided format and don't use "from now" time autoformat.
+     */
+    function timeForJs ($iUnixTimestamp, $sFormatIdentifier = BX_FORMAT_DATE, $bForceFormat = false)
+    {
+        $sDateUTC = bx_time_utc ($iUnixTimestamp);
+        return $this->timeForJsFullDate ($sDateUTC, $sFormatIdentifier, $bForceFormat);
+    }
+
+    /**
+     * Same as @see timeForJs but instead of unxtimestamp full date format is used (ex: 2005-08-15T15:52:01) as passec date param
+     */ 
+    function timeForJsFullDate ($sDateUTC, $sFormatIdentifier = BX_FORMAT_DATE, $bForceFormat = false)
+    {
+        return '<time datetime="' . $sDateUTC . '" data-bx-format="' . getParam($sFormatIdentifier) . '" data-bx-autoformat="' . ($bForceFormat ? 0 : getParam('sys_format_timeago')) . '">' . $sDateUTC . '</time>';
+    }
+    
+    function statusOnOff ($mixed, $isMsg = false)
+    {
+        if ((is_bool($mixed) && !$mixed) || (is_string($mixed) && 'fail' == $mixed))
+            return '<i class="sys-icon circle col-red2"></i> ' . ($isMsg ? _t('_sys_off') : '');
+        elseif (is_string($mixed) && 'warn' == $mixed)
+            return '<i class="sys-icon circle col-red3"></i> ' . ($isMsg ? _t('_sys_warn') : '');
+        elseif (is_string($mixed) && 'undef' == $mixed)
+            return '<i class="sys-icon circle col-gray"></i> ' . ($isMsg ? _t('_undefined') : '');
+        else
+            return '<i class="sys-icon circle col-green1"></i> ' . ($isMsg ? _t('_sys_on') : '');
+    }
+
+    /**
+     * Ouputs HTML5 video player.
+     * @param $sUrlPoster video poster image
+     * @param $sUrlMP4 .mp4 video
+     * @param $sUrlMP4Hd .mp4 video in better quality
+     * @param $aAttrs custom attributes, defaults are: controls="" preload="none" autobuffer=""
+     * @param $sStyles custom styles, defaults are: width:100%; height:auto;
+     */
+    function videoPlayer ($sUrlPoster, $sUrlMP4, $sUrlMP4Hd = '', $aAttrs = false, $sStyles = false, $bDynamicMode = false)
+    {
+        $oPlayer = BxDolPlayer::getObjectInstance();
+        if(!$oPlayer)
+            return '';
+
+        if($sStyles === false)
+            $sStyles = 'width:100%; height:auto;';
+
+        return $oPlayer->getCodeVideo (BX_PLAYER_STANDARD, array(
+            'poster' => $sUrlPoster,
+            'mp4' => array('sd' => $sUrlMP4, 'hd' => $sUrlMP4Hd),
+            'attrs' => $aAttrs,
+            'styles' => $sStyles,
+        ), $bDynamicMode);
+    }
+
+    protected function getInjection($sPrefix)
+    {
+        $sContent = '';
+
+        $aMethods = get_class_methods($this);
+        foreach($aMethods as $sMethod)
+            if(preg_match("/^(" . $sPrefix . ")[A-Z].+$/", $sMethod))
+                $sContent .= $this->$sMethod();
+
+        return $sContent;
+    }
+
+    protected function getInjHeadLiveUpdates() 
+    {
+        return BxDolLiveUpdates::getInstance()->init();
+    }
+    
+    protected function getInjHeaderPushNotifications() 
+    {
+        $iProfileId = bx_get_logged_profile_id();
+        if(empty($iProfileId))
+            return '';
+
+        $sAppId = getParam('sys_push_app_id');
+        if(empty($sAppId))
+            return '';
+
+        $aTags = BxDolPush::getTags($iProfileId);
+        if (!$aTags)
+            return '';
+
+        $sShortName = getParam('sys_push_short_name');
+        $sSafariWebId = getParam('sys_push_safari_id');
+
+        $sSubfolder = '/plugins_public/onesignal/';
+        $aUrl = parse_url(BX_DOL_URL_ROOT);
+        if(!empty($aUrl['path'])) {
+            $sPath = trim($aUrl['path'], '/');
+            if(!empty($sPath))
+                $sSubfolder = '/' . $sPath . $sSubfolder;
+        }
+
+        $this->_oTemplate->addJs(array(
+            'https://cdn.onesignal.com/sdks/OneSignalSDK.js',
+            'BxDolPush.js',
+        ));
+
+        $sJsClass = 'BxDolPush';
+        $sJsObject = 'oBxDolPush';
+
+        $sContent = "var " . $sJsObject . " = new " . $sJsClass . "(" . json_encode(array(
+            'sObjName' => $sJsObject,
+            'sSiteName' => getParam('site_title'),
+            'aTags' => $aTags,
+            'sAppId' => $sAppId,
+            'sShortName' => $sShortName,
+            'sSafariWebId' => $sSafariWebId,
+            'sSubfolder' => $sSubfolder,
+            'sNotificationUrl' => BX_DOL_URL_ROOT,
+            'sTxtNotificationRequest' => _t('_sys_push_notification_request', getParam('site_title')),
+            'sTxtNotificationRequestYes' => _t('_sys_push_notification_request_yes'),
+            'sTxtNotificationRequestNo' => _t('_sys_push_notification_request_no'),
+        )) . ");";
+
+        return $this->_oTemplate->_wrapInTagJsCode($sContent);
+    }
+
+    protected function getInjHeaderPopupLoading() 
+    {
+        return $this->transBox('bx-popup-loading', $this->_oTemplate->parsePageByName('popup_loading.html', array()), true);  
+    }
+    
+    protected function getInjFooterMenuLoading() 
+    {
+        return $this->_oTemplate->parsePageByName('menu_loading.html', array());  
+    }
+
+    protected function getInjFooterPopupMenus() 
+    {
+        $sContent = '';
+
+        $oSearch = new BxTemplSearch();
+        $oSearch->setLiveSearch(true);
+        $sContent .= $this->_oTemplate->parsePageByName('search.html', array(
+            'search_form' => $oSearch->getForm(BX_DB_CONTENT_ONLY),
+            'results' => $oSearch->getResultsContainer(),
+        ));
+
+        $sContent .= $this->_oTemplate->getMenu ('sys_site');
+        if(isLogged()) {
+            $sContent .= $this->_oTemplate->getMenu ('sys_add_content');
+            $sContent .= $this->_oTemplate->getMenu ('sys_account_popup');
+        }
+
+        return $sContent;
+    }
+
+    protected function getInjFooterPopups() 
+    {
+        $sContent = '';
+
+        $sContent .= $this->getPopupAlert();
+        $sContent .= $this->getPopupConfirm();
+        $sContent .= $this->getPopupPrompt();
+
+        return $sContent;
+    }
+
+    protected function getInjFooterEmbed() 
+    {    
+        // Load embed files
+        $oEmbed = BxDolEmbed::getObjectInstance(false);
+        if ($oEmbed) 
+            return $oEmbed->addJsCss();
+
+        return '';
+    }
+}
+
+/** @} */

+ 308 - 0
upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseVote.php

@@ -0,0 +1,308 @@
+<?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
+ * @{
+ */
+
+/**
+ * @see BxDolVote
+ */
+class BxBaseVote extends BxDolVote
+{
+    protected $_bCssJsAdded;
+
+    protected $_sJsClsName;
+    protected $_sJsObjName;
+    protected $_sStylePrefix;
+
+    protected $_aHtmlIds;
+
+    protected $_sTmplNameLegend;
+    protected $_sTmplNameByList;
+
+    public function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_aElementDefaults = array();
+
+        $this->_bCssJsAdded = false;
+        $this->_sStylePrefix = 'bx-vote';
+
+        $this->_sJsClsName = 'BxDolVote';
+        $this->_sJsObjName = 'oVote' . bx_gen_method_name($sSystem, array('_' , '-')) . $iId;
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $sSystem) . '-' . $iId;
+        $this->_aHtmlIds = array(
+            'counter' => 'bx-vote-counter-' . $sHtmlId,
+            'by_popup' => 'bx-vote-by-popup-' . $sHtmlId
+        );
+
+        $this->_sTmplNameLegend = 'vote_legend.html';
+        $this->_sTmplNameByList = 'vote_by_list.html';
+
+        $this->_sTmplContentElementBlock = $this->_oTemplate->getHtml('vote_element_block.html');
+        $this->_sTmplContentElementInline = $this->_oTemplate->getHtml('vote_element_inline.html');
+        $this->_sTmplContentCounter = $this->_oTemplate->getHtml('vote_counter.html');
+    }
+
+    public function getJsClassName()
+    {
+        return $this->_sJsClsName;
+    }
+
+    public function getJsObjectName()
+    {
+        return $this->_sJsObjName;
+    }
+
+    public function getJsScript($aParams = array())
+    {
+        $sJsObjName = $this->getJsObjectName();
+        $sJsObjClass = $this->getJsClassName();
+
+        $bDynamicMode = isset($aParams['dynamic_mode']) && (bool)$aParams['dynamic_mode'] === true;
+
+        $sCode = "if(window['" . $sJsObjName . "'] == undefined) var " . $sJsObjName . " = new " . $sJsObjClass . "(" . json_encode(array(
+            'sObjName' => $sJsObjName,
+            'sSystem' => $this->getSystemName(),
+            'iAuthorId' => $this->_getAuthorId(),
+            'iObjId' => $this->getId(),
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'sStylePrefix' => $this->_sStylePrefix,
+            'aHtmlIds' => $this->_aHtmlIds,
+            'aRequestParams' => $this->_prepareRequestParamsData($aParams)
+        )) . ");";
+
+        return $this->_oTemplate->_wrapInTagJsCode($sCode);
+    }
+
+    public function getJsClick($iValue = 0)
+    {
+        return $this->getJsObjectName() . '.vote(this, ' . $iValue . ')';
+    }
+
+    public function getJsClickCounter($aParams = array())
+    {
+        return $this->getJsObjectName() . '.toggleByPopup(this)';
+    }
+
+    public function getCounter($aParams = array())
+    {
+        $aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        $bDynamicMode = isset($aParams['dynamic_mode']) && (bool)$aParams['dynamic_mode'] === true;
+        $bShowEmpty = isset($aParams['show_counter_empty']) && (bool)$aParams['show_counter_empty'] === true;
+        $bShowActive = $this->isAllowedVoteViewVoters() && (!isset($aParams['show_counter_active']) || (bool)$aParams['show_counter_active'] === true);
+        $bShowScript = !isset($aParams['show_script']) || (bool)$aParams['show_script'] === true;
+
+        $sClass = $this->_sStylePrefix . '-counter ' . $this->_sStylePrefix . '-counter-' . $this->_sType;
+        if(!empty($aParams['class_counter']))
+            $sClass .= $aParams['class_counter'];
+
+        $aTmplVarsAttrs = array(
+            array('key' => 'class', 'value' => $sClass),
+        );
+
+        if($bShowActive)
+            $aTmplVarsAttrs = array_merge($aTmplVarsAttrs, array(
+                array('key' => 'href', 'value' => 'javascript:void(0)'),
+                array('key' => 'title', 'value' => bx_html_attribute($this->_getTitleDoBy($aParams))),
+                array('key' => 'onclick', 'value' => 'javascript:' . $this->getJsClickCounter($aParams))
+            ));
+
+        $sHtmlId = isset($aParams['id_counter']) ? $aParams['id_counter'] : $this->_aHtmlIds['counter'];
+        if(!empty($sHtmlId))
+            $aTmplVarsAttrs[] = array('key' => 'id', 'value' => $sHtmlId);
+
+        $aVote = !empty($aParams['vote']) && is_array($aParams['vote']) ? $aParams['vote'] : $this->_getVote();
+        $sContent = $bShowEmpty || (int)$aVote['count'] > 0 ? $this->_getCounterLabel($aVote['count'], $aParams) : '';
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentCounter(), array(
+            'bx_if:show_text' => array(
+                'condition' => !$bShowActive,
+                'content' => array(
+                    'bx_repeat:attrs' => $aTmplVarsAttrs,
+                    'content' => $sContent
+                )
+            ),
+            'bx_if:show_link' => array(
+                'condition' => $bShowActive,
+                'content' => array(
+                    'bx_repeat:attrs' => $aTmplVarsAttrs,
+                    'content' => $sContent
+                )
+            ),
+            'bx_repeat:attrs' => $aTmplVarsAttrs,
+            'content' => $sContent,
+            'script' => $bShowScript ? $this->getJsScript($aParams) : ''
+        ));
+    }
+
+    public function getLegend($aParams = array())
+    {
+        return '';
+    }
+
+    public function getElementBlock($aParams = array())
+    {
+        $aParams['usage'] = BX_DOL_VOTE_USAGE_BLOCK;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElementInline($aParams = array())
+    {
+        $aParams['usage'] = BX_DOL_VOTE_USAGE_INLINE;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElement($aParams = array())
+    {
+        $sMethod = '_getTmplContentElement' . bx_gen_method_name(!empty($aParams['usage']) ? $aParams['usage'] : BX_DOL_VOTE_USAGE_DEFAULT);
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        $aTmplVars = $this->_getTmplVarsElement($aParams);
+        if(empty($aTmplVars) || !is_array($aTmplVars))
+            return '';
+
+        return $this->_oTemplate->parseHtmlByContent($this->$sMethod(), $aTmplVars);
+    }
+
+    /**
+     * Internal methods.
+     * 
+     * This method should be overwritten by subclass.
+     */
+    protected function _getTmplVarsElement($aParams = array())
+    {
+    	$aParams = array_merge($this->_aElementDefaults, $aParams);
+
+    	$bDynamicMode = isset($aParams['dynamic_mode']) && (bool)$aParams['dynamic_mode'] === true;
+        $bShowCounterEmpty = isset($aParams['show_counter_empty']) && (bool)$aParams['show_counter_empty'] === true;
+
+        $iObjectId = $this->getId();
+        $iAuthorId = $this->_getAuthorId();
+
+        $bCount = $this->_isCount();
+        $isAllowedVote = $this->isAllowedVote();
+        $isAllowedVoteView = $this->isAllowedVoteView();
+        $aParams['is_voted'] = $this->isPerformed($iObjectId, $iAuthorId);
+        $aParams['track'] = $aParams['is_voted'] ? $this->_getTrack($iObjectId, $iAuthorId) : array();
+
+        //--- Do Vote
+        $bTmplVarsDoVote = $this->_isShowDoVote($aParams, $isAllowedVote, $bCount);
+        $aTmplVarsDoVote = array();
+        if($bTmplVarsDoVote)
+            $aTmplVarsDoVote = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'do_vote' => $this->_getDoVote($aParams, $isAllowedVote),
+            );
+
+        //--- Counter
+        $bTmplVarsCounter = $this->_isShowCounter($aParams, $isAllowedVote, $isAllowedVoteView, $bCount);
+        $aTmplVarsCounter = array();
+        if($bTmplVarsCounter)
+            $aTmplVarsCounter = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'bx_if:show_hidden' => array(
+                    'condition' => !$bCount && !$bShowCounterEmpty,
+                    'content' => array()
+                ),
+                'counter' => $this->getCounter(array_merge($aParams, array('show_script' => false)))
+            );
+
+        //--- Legend
+        $bTmplVarsLegend = $this->_isShowLegend($aParams, $isAllowedVote, $isAllowedVoteView, $bCount);
+        $aTmplVarsLegend = array();
+        if($bTmplVarsLegend)
+            $aTmplVarsLegend = array(
+                'legend' => $this->getLegend($aParams)
+            );
+
+        if(!$bTmplVarsDoVote && !$bTmplVarsCounter && !$bTmplVarsLegend)
+            return array();
+
+        $sClass = $this->_sStylePrefix . '-' . $this->_sType;
+        if(!empty($aParams['class_element']))
+            $sClass .= $aParams['class_element'];
+
+        return array(
+            'style_prefix' => $this->_sStylePrefix,
+            'html_id' => $this->_aHtmlIds['main'],
+            'class' => $sClass . ' ' . $this->_aHtmlIds['main'],
+            'bx_if:show_vote_data' => array(
+                'condition' => false,
+                'content' => array()
+            ),
+            'bx_if:show_do_vote' => array(
+                'condition' => $bTmplVarsDoVote,
+                'content' => $aTmplVarsDoVote
+            ),
+            'bx_if:show_counter' => array(
+                'condition' => $bTmplVarsCounter,
+                'content' => $aTmplVarsCounter
+            ),
+            'bx_if:show_legend' => array(
+            	'condition' => $bTmplVarsLegend,
+            	'content' => $aTmplVarsLegend
+            ),
+            'script' => $this->getJsScript($aParams)
+        );
+    }
+
+    protected function _getDoVote($aParams = array(), $isAllowedVote = true)
+    {
+        return '';
+    }
+
+    protected function _getCounterLabel($iCount, $aParams = array())
+    {
+        return _t('_vote_counter', $iCount);
+    }
+
+    protected function _getVotedBy($aParams = array())
+    {
+        $aTmplUsers = array();
+
+        $aUserIds = $this->_oQuery->getPerformedBy($this->getId());
+        foreach($aUserIds as $iUserId) {
+            list($sUserName, $sUserUrl, $sUserIcon, $sUserUnit) = $this->_getAuthorInfo($iUserId);
+            $aTmplUsers[] = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'user_unit' => $sUserUnit
+            );
+        }
+
+        if(empty($aTmplUsers))
+            $aTmplUsers = MsgBox(_t('_Empty'));
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameByList, array(
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => '',
+            'bx_repeat:list' => $aTmplUsers
+        ));
+    }
+
+    protected function _isShowDoVote($aParams, $isAllowedVote, $bCount)
+    {
+        return !isset($aParams['show_do_vote']) || (bool)$aParams['show_do_vote'] === true;
+    }
+
+    protected function _isShowCounter($aParams, $isAllowedVote, $isAllowedVoteView, $bCount)
+    {
+        return isset($aParams['show_counter']) && (bool)$aParams['show_counter'] === true && $isAllowedVoteView && ($isAllowedVote || $bCount);
+    }
+
+    protected function _isShowLegend($aParams, $isAllowedVote, $isAllowedVoteView, $bCount)
+    {
+        return false;
+    }
+}
+
+/** @} */

+ 429 - 0
upgrade/files/11.0.1-11.0.2/files/template/scripts/BxBaseVoteReactions.php

@@ -0,0 +1,429 @@
+<?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
+ * @{
+ */
+
+/**
+ * @see BxDolVote, BxDolVoteLikes
+ */
+class BxBaseVoteReactions extends BxDolVoteReactions
+{
+    protected $_sTmplNameBySummary;
+    protected $_sTmplContentCounterWrapper = '';
+
+    public function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_sJsClsName .= 'Reactions';
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $sSystem) . '-' . $iId;
+        $this->_aHtmlIds = array_merge($this->_aHtmlIds, array(
+            'main' => 'bx-vr-' . $sHtmlId,
+            'do_menu' => 'bx-vr-do-menu-' . $sHtmlId,
+            'do_popup' => 'bx-vr-do-popup-' . $sHtmlId
+        ));
+
+        $this->_aElementDefaults = array(
+            'show_do_vote_as_button' => false,
+            'show_do_vote_as_button_small' => false,
+            'show_do_vote_icon' => true,
+            'show_do_vote_label' => false,
+            'show_counter' => false,
+            'show_counter_empty' => true,
+            'show_counter_style' => self::$_sCounterStyleDivided, //--- Alloved styles are 'divided' and 'compound'
+            'show_legend' => false,
+            'show_script' => true
+        );
+
+        $this->_sTmplNameBySummary = 'vote_by_summary_reactions.html';
+        $this->_sTmplNameByList = 'vote_by_list_reactions.html';
+
+        $this->_sTmplContentDoActionLabel = $this->_oTemplate->getHtml('vote_do_vote_label_reactions.html');
+        $this->_sTmplContentCounterLabel = $this->_oTemplate->getHtml('vote_counter_label_reactions.html');
+        $this->_sTmplContentCounterWrapper = $this->_oTemplate->getHtml('vote_counter_wrapper_reactions.html');
+    }
+
+    public function getJsClick($iValue = 0)
+    {
+        if(empty($iValue))
+            $iValue = $this->getValue();
+
+        return $this->getJsObjectName() . '.toggleDoPopup(this, ' . $iValue . ')';
+    }
+
+    public function getJsClickDo($sReaction, $iValue = 0)
+    {
+        if(empty($iValue))
+            $iValue = $this->getValue();
+
+        return $this->getJsObjectName() . '.vote(this, ' . $iValue . ', \'' . $sReaction . '\')';
+    }
+
+    public function getJsClickCounter($aParams = array())
+    {
+        $sJsObject = $this->getJsObjectName();
+        $sJsMethod = 'toggleByPopup';
+
+        if(isset($aParams['show_counter_style']) && $aParams['show_counter_style'] == self::$_sCounterStyleCompound)
+            return $sJsObject . '.' . $sJsMethod . '(this)';
+
+        $sReaction = !empty($aParams['reaction']) ? $aParams['reaction'] : $this->_sDefault;
+        return $sJsObject . '.' . $sJsMethod . '(this, \'' . $sReaction . '\')';
+    }
+
+    public function getCounter($aParams = array())
+    {
+        $sDefault = $this->_aElementDefaults['show_counter_style'];
+        $sCounterStyle = !empty($aParams['show_counter_style']) ? $aParams['show_counter_style'] : $sDefault;
+
+        $sMethodPrefix = '_getCounter';
+        $sMethod = $sMethodPrefix . bx_gen_method_name($sCounterStyle);
+        if(!method_exists($this, $sMethod))
+            $sMethod = $sMethodPrefix . bx_gen_method_name($sDefault);
+
+        return $this->$sMethod($aParams);
+    }
+
+    public function _getCounterDivided($aParams = array())
+    {
+        $bDynamicMode = isset($aParams['dynamic_mode']) && (bool)$aParams['dynamic_mode'] === true;
+        $bShowCounterEmpty = isset($aParams['show_counter_empty']) && (bool)$aParams['show_counter_empty'] === true;
+        $bShowScript = !isset($aParams['show_script']) || (bool)$aParams['show_script'] === true;
+
+        $bVote = $this->_isVote();
+        $aVote = $this->_getVote();
+        $aReactions = $this->getReactions();
+
+        $sClass = isset($aParams['class_counter']) ? $aParams['class_counter'] : '';
+        if(isset($aParams['show_do_vote_as_button_small']) && (bool)$aParams['show_do_vote_as_button_small'] === true)
+            $sClass .= ' bx-btn-small-height';
+        else if(isset($aParams['show_do_vote_as_button']) && (bool)$aParams['show_do_vote_as_button'] === true)
+            $sClass .= ' bx-btn-height';
+        $sClass .= ' bx-def-margin-sec-right';
+
+        $aParams['id_counter'] = '';
+
+        $sResult = '';
+        foreach($aReactions as $sName) {
+            $iCount = (int)$aVote['count_' . $sName];
+
+            $aParams['class_counter'] = ($iCount == 0 && (!$bShowCounterEmpty || $sName != $this->_sDefault || $bVote) ? ' bx-vc-hidden' : '') . ' ' . $sName . ' ' . $sClass;
+            $aParams['reaction'] = $sName;
+            $aParams['vote'] = array(
+                'count' => $iCount,
+                'sum' => $aVote['sum_' . $sName],
+                'rate' => $aVote['rate_' . $sName],
+            );
+
+            $sResult .= parent::getCounter(array_merge($aParams, array('show_script' => false)));
+        }
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentCounterWrapper(), array(
+            'html_id' => $this->_aHtmlIds['counter'],
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => $this->_aHtmlIds['counter'] . (!$bVote && !$bShowCounterEmpty ? ' bx-vc-hidden' : ''),
+            'type' => $this->_sType,
+            'style' => self::$_sCounterStyleDivided,
+            'bx_if:show_link' => array(
+                'condition' => false,
+                'content' => array()
+            ),
+            'bx_if:show_text' => array(
+                'condition' => true,
+                'content' => array(
+                    'counter' => $sResult,
+                )
+            ),
+            'script' => $bShowScript ? $this->getJsScript($aParams) : ''
+        ));
+    }
+
+    public function _getCounterCompound($aParams = array())
+    {
+        $bDynamicMode = isset($aParams['dynamic_mode']) && (bool)$aParams['dynamic_mode'] === true;
+        $bShowCounterEmpty = isset($aParams['show_counter_empty']) && (bool)$aParams['show_counter_empty'] === true;
+        $bShowScript = !isset($aParams['show_script']) || (bool)$aParams['show_script'] === true;
+
+        $bVote = $this->_isVote();
+        $aVote = $this->_getVote();
+        $aReactions = $this->getReactions();
+
+        $sClass = isset($aParams['class_counter']) ? $aParams['class_counter'] : '';
+        if(isset($aParams['show_do_vote_as_button_small']) && (bool)$aParams['show_do_vote_as_button_small'] === true)
+            $sClass .= ' bx-btn-small-height';
+        else if(isset($aParams['show_do_vote_as_button']) && (bool)$aParams['show_do_vote_as_button'] === true)
+            $sClass .= ' bx-btn-height';
+
+        $aParams['id_counter'] = '';
+
+        $sResult = '';
+        $iResultC = $iResultS = 0;
+        foreach($aReactions as $sName) {
+            $iCount = (int)$aVote['count_' . $sName];
+
+            $aParams = array_merge($aParams, array(
+                'show_counter_active' => false,
+                'show_counter_label_icon' => true,
+                'show_counter_label_text' => false,
+                'class_counter' => ($iCount == 0 && (!$bShowCounterEmpty || $sName != $this->_sDefault || $bVote) ? ' bx-vc-hidden' : '') . ' ' . $sName . ' ' . $sClass,
+                'reaction' => $sName,
+                'vote' => array(
+                    'count' => $iCount,
+                    'sum' => $aVote['sum_' . $sName],
+                    'rate' => $aVote['rate_' . $sName],
+                )
+            ));
+
+            $iResultC += $iCount;
+            $iResultS += (int)$aVote['sum_' . $sName];
+            $sResult .= trim(parent::getCounter(array_merge($aParams, array('show_script' => false))));
+        }
+
+        $aParams = array_merge($aParams, array(
+            'show_counter_active' => false,
+            'show_counter_label_icon' => false,
+            'show_counter_label_text' => true,
+            'class_counter' => ' total-count ' . $sClass,
+            'reaction' => '',
+            'vote' => array(
+                'count' => $iResultC,
+                'sum' => $iResultS,
+                'rate' => $iResultC > 0 ? round($iResultS / $iResultC, 2) : 0,
+            )
+        ));
+        $sResult .= parent::getCounter(array_merge($aParams, array('show_script' => false)));
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentCounterWrapper(), array(
+            'html_id' => $this->_aHtmlIds['counter'],
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => $this->_aHtmlIds['counter'] . (!$bVote && !$bShowCounterEmpty ? ' bx-vc-hidden' : ''),
+            'type' => $this->_sType,
+            'style' => self::$_sCounterStyleCompound,
+            'bx_if:show_link' => array(
+                'condition' => true,
+                'content' => array(
+                    'href' => 'javascript:void(0)',
+                    'onclick' => 'javascript:' . $this->getJsClickCounter($aParams),
+                    'title' => bx_html_attribute($this->_getTitleDoBy($aParams)),
+                    'counter' => $sResult,
+                )
+            ),
+            'bx_if:show_text' => array(
+                'condition' => false,
+                'content' => array()
+            ),
+            'script' => $bShowScript ? $this->getJsScript($aParams) : ''
+        ));
+    }
+
+    public function getElement($aParams = array())
+    {
+        $sClass = $this->_sStylePrefix . '-' . $this->_sType;
+        if(isset($aParams['show_do_vote_as_button_small']) && (bool)$aParams['show_do_vote_as_button_small'] === true)
+            $sClass .=  '-button-small';
+        else if(isset($aParams['show_do_vote_as_button']) && (bool)$aParams['show_do_vote_as_button'] === true)
+            $sClass .= '-button';
+
+        $aParams['class_element'] = isset($aParams['class_element']) ? $aParams['class_element'] : '';
+        $aParams['class_element'] .= ' ' . $sClass;
+
+        return parent::getElement($aParams);
+    }
+
+    /**
+     * Internal methods.
+     */
+    protected function _isShowDoVote($aParams, $isAllowedVote, $bCount)
+    {
+        $bResult = parent::_isShowDoVote($aParams, $isAllowedVote, $bCount);
+        if(!$bResult)
+            return $bResult;
+
+        return $isAllowedVote || $bCount;
+    }
+
+    protected function _getDoVote($aParams = array(), $isAllowedVote = true)
+    {
+        $bUndo = $this->isUndo();
+    	$bVoted = isset($aParams['is_voted']) && (bool)$aParams['is_voted'] === true;
+        $bShowDoVoteAsButtonSmall = isset($aParams['show_do_vote_as_button_small']) && (bool)$aParams['show_do_vote_as_button_small'] === true;
+        $bShowDoVoteAsButton = !$bShowDoVoteAsButtonSmall && isset($aParams['show_do_vote_as_button']) && (bool)$aParams['show_do_vote_as_button'] === true;
+        $bDisabled = !$isAllowedVote || ($bVoted && !$bUndo);
+
+        $sClass = '';
+        if($bShowDoVoteAsButton)
+            $sClass = ' bx-btn';
+        else if ($bShowDoVoteAsButtonSmall)
+            $sClass = ' bx-btn bx-btn-small';
+
+        $sJsClick = '';
+        if(!$bDisabled) {
+            if($bVoted && $bUndo) {
+                $sClass = ' ' . $this->_sStylePrefix . '-voted' . $sClass;
+                $sJsClick = $this->getJsClickDo($aParams['track']['reaction']);
+            }
+            else 
+                $sJsClick = $this->getJsClick();
+        }
+        else
+            $sClass .= $bShowDoVoteAsButton || $bShowDoVoteAsButtonSmall ? ' bx-btn-disabled' : ' ' . $this->_sStylePrefix . '-disabled';
+
+        return $this->_oTemplate->parseLink('javascript:void(0)', $this->_getDoVoteLabel($aParams), array(
+            'class' => $this->_sStylePrefix . '-do-vote' . $sClass,
+            'title' => _t($this->_getTitleDoWithTrack($bVoted, $aParams['track'])),
+            'onclick' => $sJsClick,
+        ));
+    }
+
+    protected function _getDoVoteLabel($aParams = array())
+    {
+    	$bVoted = isset($aParams['is_voted']) && (bool)$aParams['is_voted'] === true;
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentDoActionLabel(), array(
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:show_icon' => array(
+                'condition' => isset($aParams['show_do_vote_icon']) && (bool)$aParams['show_do_vote_icon'] === true,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'name' => $this->_getIconDoWithTrack($bVoted, $aParams['track'])
+                )
+            ),
+            'bx_if:show_text' => array(
+                'condition' => isset($aParams['show_do_vote_label']) && (bool)$aParams['show_do_vote_label'] === true,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'text' => _t($this->_getTitleDoWithTrack($bVoted, $aParams['track']))
+                )
+            )
+        ));
+    }
+
+    public function _getDoVotePopup($iValue = 0)
+    {
+        if(empty($iValue))
+            $iValue = $this->getValue();
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_sMenuDoVote);
+        if(!$oMenu)
+            return '';
+
+        $oMenu->setParams(array(
+            'object' => &$this,
+            'value' => $iValue,
+        ));
+        return $oMenu->getCode();
+    }
+
+    protected function _getCounterLabel($iCount, $aParams = array())
+    {
+        $sReaction = !empty($aParams['reaction']) ? $aParams['reaction'] : $this->_sDefault;
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplContentCounterLabel(), array(
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:show_icon' => array(
+                'condition' => !isset($aParams['show_counter_label_icon']) || (bool)$aParams['show_counter_label_icon'] === true,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'name' => $this->getIcon($sReaction)
+                )
+            ),
+            'bx_if:show_text' => array(
+                'condition' => !isset($aParams['show_counter_label_text']) || (bool)$aParams['show_counter_label_text'] === true,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'text' => parent::_getCounterLabel($iCount)
+                )
+            )
+        ));
+    }
+
+    protected function _getVotedBy($aParams = array())
+    {
+        if(!isset($aParams['reaction']))
+            return $this->_getVotedBySummary($aParams);
+
+        $bSummary = $aParams['reaction'] == 'summary';
+
+        $aBrowseParams = array('type' => 'by', 'object_id' => $this->getId());
+        if(!$bSummary)
+            $aBrowseParams['reaction'] = $aParams['reaction'];
+
+        $aValues = $this->_oQuery->getPerformed($aBrowseParams);
+
+        $aTmplUsers = array();
+        foreach($aValues as $mValue) {
+            $mValue = is_array($mValue) ? $mValue : array('author_id' => (int)$mValue, 'reaction' => '');
+
+            list($sUserName, $sUserUrl, $sUserIcon, $sUserUnit) = $this->_getAuthorInfo($mValue['author_id']);
+            $aTmplUsers[] = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'user_unit' => $sUserUnit,
+                'bx_if:show_reaction' => array(
+                    'condition' => $bSummary,
+                    'content' => array(
+                        'style_prefix' => $this->_sStylePrefix,
+                        'icon' => $bSummary ? $this->getIcon($mValue['reaction']) : ''
+                    )
+                )
+            );
+        }
+
+        if(empty($aTmplUsers))
+            $aTmplUsers = MsgBox(_t('_Empty'));
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameByList, array(
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => ' ' . $this->_sStylePrefix . '-bl-' . $aParams['reaction'],
+            'bx_repeat:list' => $aTmplUsers
+        ));
+    }
+
+    protected function _getVotedBySummary($aParams = array())
+    {
+        $sJsObject = $this->getJsObjectName();
+
+        $sTxtSummary = _t('_vote_do_by_summary');
+        $aReactions = array_merge(array('summary'), $this->getReactions());
+
+        $aMenuItems = array();
+        $aTmplVarsLists = array();
+        foreach ($aReactions as $sReaction) {
+            if($sReaction == $this->_sDefault)
+                continue;
+
+            $bSummary = $sReaction == 'summary';
+
+            $sName = $this->_sStylePrefix . '-' . $sReaction;
+            $sTitle = !$bSummary ? $this->_oTemplate->parseIcon($this->getIcon($sReaction)) : $sTxtSummary;
+            $sTitleAttr = !$bSummary ? _t('_vote_do_by_x_reaction', _t($this->_aDataList[$sReaction]['title'])) : $sTxtSummary;
+
+            $aMenuItems[] = array('id' => $sName, 'name' => $sName, 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => 'javascript:' . $sJsObject . '.changeVotedBy(this, \'' . $sReaction . '\')', 'target' => '_self', 'title' => $sTitle, 'title_attr' => $sTitleAttr, 'active' => 1);
+
+            $aTmplVarsLists[] = array(
+                'content' => $this->_getVotedBy(array('reaction' => $sReaction))
+            );
+        }
+
+        $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sStylePrefix . '-voted-by', 'menu_items' => $aMenuItems));
+        $oMenu->setSelected('', $this->_sStylePrefix . '-summary');
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameBySummary, array(
+            'style_prefix' => $this->_sStylePrefix,
+            'title' => '',
+            'menu' => $oMenu->getCode(),
+            'bx_repeat:lists' => $aTmplVarsLists
+        ));
+    }
+
+    protected function _getTmplContentCounterWrapper()
+    {
+        return $this->_sTmplContentCounterWrapper;
+    }
+}
+
+/** @} */

+ 3 - 0
upgrade/files/11.0.1-11.0.2/script.php

@@ -0,0 +1,3 @@
+<?php
+
+    return true;

+ 4 - 0
upgrade/files/11.0.1-11.0.2/sql.sql

@@ -0,0 +1,4 @@
+
+-- Last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '11.0.2' WHERE (`version` = '11.0.1') AND `name` = 'system';