Sfoglia il codice sorgente

Upgrade script from 9.0.0.RC12 to 9.0.0.RC13

Alex Trofimov 8 anni fa
parent
commit
8f3ceb2a4e
28 ha cambiato i file con 12727 aggiunte e 0 eliminazioni
  1. 9 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/check.php
  2. 735 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/classes/BxDolAcl.php
  3. 1009 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/classes/BxDolSearch.php
  4. 1059 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/js/functions.js
  5. 1721 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/utils.inc.php
  6. 12 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/version.inc.php
  7. 111 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/files/classes/BxBaseModFilesFormUpload.php
  8. 74 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/files/classes/BxBaseModFilesFormsEntryHelper.php
  9. 508 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php
  10. 400 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/general/classes/BxBaseModGeneralMenuViewActions.php
  11. 292 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/general/classes/BxBaseModGeneralTemplate.php
  12. 683 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/groups/classes/BxBaseModGroupsModule.php
  13. 479 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/notifications/classes/BxBaseModNotificationsDb.php
  14. 205 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/notifications/classes/BxBaseModNotificationsModule.php
  15. 77 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/notifications/classes/BxBaseModNotificationsResponse.php
  16. 198 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfileAlertsResponse.php
  17. 1262 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfileModule.php
  18. 50 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfilePrivacy.php
  19. 37 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfileVote.php
  20. 196 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/studio/classes/BxDolStudioDashboard.php
  21. 391 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/studio/classes/BxDolStudioStore.php
  22. 1055 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/studio/template/scripts/BxBaseStudioStore.php
  23. 778 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/css/common.css
  24. 644 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/scripts/BxBaseFunctions.php
  25. 246 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/scripts/BxBaseMenu.php
  26. 487 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/scripts/BxBasePage.php
  27. 3 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/script.php
  28. 6 0
      upgrade/files/9.0.0.RC12-9.0.0.RC13/sql.sql

+ 9 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/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 ('9.0.0.RC12' == $sVer || '9.0.0-RC12' == $sVer)
+    $mixCheckResult = true;
+
+return $mixCheckResult;

+ 735 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/classes/BxDolAcl.php

@@ -0,0 +1,735 @@
+<?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
+ * @{
+ */
+
+/**
+ * Message constants passed to _t_ext() function by checkAction()
+ *
+ * NOTE: checkAction() returns language dependent messages
+ */
+define('CHECK_ACTION_MESSAGE_NOT_ALLOWED',			"_sys_acl_action_not_allowed");
+define('CHECK_ACTION_MESSAGE_LIMIT_REACHED',		"_sys_acl_action_limit_reached");
+define('CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD',	"_sys_acl_action_every_period");
+define('CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE',	"_sys_acl_action_not_allowed_before");
+define('CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER',	"_sys_acl_action_not_allowed_after");
+
+define('CHECK_ACTION_MESSAGE_UNAUTHENTICATED',	    "_sys_acl_action_unauthenticated");
+define('CHECK_ACTION_MESSAGE_UNCONFIRMED',			"_sys_acl_action_unconfirmed");
+define('CHECK_ACTION_MESSAGE_PENDING',			    "_sys_acl_action_pending");
+define('CHECK_ACTION_MESSAGE_SUSPENDED',			"_sys_acl_action_suspended");
+
+/**
+ * Nodes of $args array that are passed to _t_ext() function by checkAction()
+ */
+define('CHECK_ACTION_LANG_FILE_ACTION', 1);
+define('CHECK_ACTION_LANG_FILE_MEMBERSHIP', 2);
+define('CHECK_ACTION_LANG_FILE_LIMIT', 3);
+define('CHECK_ACTION_LANG_FILE_PERIOD', 4);
+define('CHECK_ACTION_LANG_FILE_AFTER', 5);
+define('CHECK_ACTION_LANG_FILE_BEFORE', 6);
+define('CHECK_ACTION_LANG_FILE_SITE_EMAIL',	7);
+
+/**
+ * Standard membership ID's
+ */
+define('MEMBERSHIP_ID_NON_MEMBER', 1);
+define('MEMBERSHIP_ID_ACCOUNT', 2);
+define('MEMBERSHIP_ID_STANDARD', 3);
+define('MEMBERSHIP_ID_UNCONFIRMED', 4);
+define('MEMBERSHIP_ID_PENDING', 5);
+define('MEMBERSHIP_ID_SUSPENDED', 6);
+define('MEMBERSHIP_ID_MODERATOR', 7);
+define('MEMBERSHIP_ID_ADMINISTRATOR', 8);
+
+/**
+ * Indices for checkAction() result array
+ */
+define('CHECK_ACTION_RESULT', 0);
+define('CHECK_ACTION_MESSAGE', 1);
+define('CHECK_ACTION_PARAMETER', 3);
+
+/**
+ * CHECK_ACTION_RESULT node values
+ */
+define('CHECK_ACTION_RESULT_ALLOWED', 0);
+define('CHECK_ACTION_RESULT_NOT_ALLOWED', 1);
+define('CHECK_ACTION_RESULT_NOT_ACTIVE', 2);
+define('CHECK_ACTION_RESULT_LIMIT_REACHED', 3);
+define('CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE', 4);
+define('CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER', 5);
+
+/**
+ * Standard period units
+ */
+define('MEMBERSHIP_PERIOD_UNIT_DAY', 'day');
+define('MEMBERSHIP_PERIOD_UNIT_WEEK', 'week');
+define('MEMBERSHIP_PERIOD_UNIT_MONTH', 'month');
+define('MEMBERSHIP_PERIOD_UNIT_YEAR', 'year');
+
+
+class BxDolAcl extends BxDolFactory implements iBxDolSingleton
+{
+    protected $oDb;
+
+    protected $_aStandardMemberships = array(
+        MEMBERSHIP_ID_NON_MEMBER => 1,
+        MEMBERSHIP_ID_ACCOUNT => 1,
+        MEMBERSHIP_ID_UNCONFIRMED => 1,
+        MEMBERSHIP_ID_PENDING => 1,
+        MEMBERSHIP_ID_SUSPENDED => 1,
+        MEMBERSHIP_ID_STANDARD => 1,
+    );
+
+    protected $_aProfileStatus2LevelMap = array (
+        BX_PROFILE_STATUS_SUSPENDED => MEMBERSHIP_ID_SUSPENDED,
+        BX_PROFILE_STATUS_PENDING => MEMBERSHIP_ID_PENDING,
+    );
+
+    protected $_aLevel2MessageMap = array (
+        MEMBERSHIP_ID_NON_MEMBER => '_sys_acl_action_unauthenticated',
+        MEMBERSHIP_ID_ACCOUNT => '_sys_acl_action_account',
+        MEMBERSHIP_ID_UNCONFIRMED => '_sys_acl_action_unconfirmed',
+        MEMBERSHIP_ID_PENDING => '_sys_acl_action_pending',
+        MEMBERSHIP_ID_SUSPENDED => '_sys_acl_action_suspended',
+    );
+
+    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();
+
+        $this->oDb = BxDolAclQuery::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);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxTemplAcl();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+	/**
+     * Get necessary condition array to use membership level in search classes
+     * @param $sContentField content table field name
+     * @param $mixedLevelId level ID or array of level IDs
+     * @return array of conditions is returned
+     */
+    public function getContentByLevelAsCondition($sContentField, $mixedLevelId)
+    {
+        return array(
+        	'restriction_sql' => ' AND (`tlm`.DateStarts IS NULL OR `tlm`.DateStarts <= NOW()) AND (`tlm`.DateExpires IS NULL OR `tlm`.DateExpires > NOW())',
+            'restriction' => array (
+                'acl_members' => array(
+                    'value' => $mixedLevelId,
+                    'field' => 'IDLevel',
+                    'operator' => is_array($mixedLevelId) ? 'in' : '=',
+                    'table' => 'tlm',
+                ),
+            ),
+            'join' => array (
+                'acl_members' => array(
+                    'type' => 'INNER',
+                    'table' => 'sys_acl_levels_members',
+                    'table_alias' => 'tlm',
+                    'mainField' => $sContentField,
+                    'onField' => 'IDMember',
+                    'joinFields' => array(),
+                ),
+                'acl_levels' => array(
+                    'type' => 'INNER',
+                    'table' => 'sys_acl_levels',
+                    'table_alias' => 'tl',
+                    'mainTable' => 'tlm',
+                    'mainField' => 'IDLevel',
+                    'onField' => 'ID',
+                    'joinFields' => array(),
+                ),
+            ),
+        );
+    }
+
+	/**
+     * Get necessary parts of SQL query to use membership levels in other queries
+     * @param $sContentTable content table name
+     * @param $sContentField content table field name
+     * @param $mixedLevelId level ID or array of level IDs
+     * @return array of SQL string parts, for now 'where' part only is returned
+     */
+    public function getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId)
+    {
+        return $this->oDb->getContentByLevelAsSQLPart($sContentTable, $sContentField, $mixedLevelId);
+    }
+
+    /**
+     * Check if member has one of the provided membership levels
+     * @param $mixedPermissions - integer value (every bit is matched with some membership ID) or an array of membership IDs to check permissions for
+     * @param $iProfileId - profile to check, if it isn't provided or is false then currently logged in profile is used.
+     * @return true if member has privided membership levels, or false if member hasn't.
+     */
+    public function isMemberLevelInSet($mixedPermissions, $iProfileId = false)
+    {
+        $iPermissions = 0;
+        if(is_array($mixedPermissions))
+            foreach($mixedPermissions as $iPermissionId)
+                $iPermissions += pow(2, $iPermissionId - 1);
+        else if(is_numeric($mixedPermissions))
+            $iPermissions = (int)$mixedPermissions;
+
+        if(!$iPermissions)
+            return false;
+
+        if(false === $iProfileId) {
+            $oProfile = BxDolProfile::getInstance();
+            $iProfileId = $oProfile ? $oProfile->id() : 0;
+        }
+
+        $aACL = $this->getMemberMembershipInfo($iProfileId);
+        return ($iPermissions & pow(2, $aACL['id'] - 1));
+    }
+
+    /**
+     * Checks if a given action is allowed for a given profile and updates action information if the
+     * action is performed.
+     *
+     * @param  int     $iProfileId     ID of a profile that is going to perform an action
+     * @param  int     $iActionId      ID of the action itself
+     * @param  boolean $bPerformAction if true, then action information is updated, i.e. action is 'performed'
+     * @return array(
+     *                                CHECK_ACTION_RESULT => CHECK_ACTION_RESULT_ constant,
+     *                                CHECK_ACTION_MESSAGE => CHECK_ACTION_MESSAGE_ constant,
+     *                                CHECK_ACTION_PARAMETER => additional action parameter (string)
+     *                                )
+     *
+     * NOTES:
+     *
+     * $aResult[CHECK_ACTION_MESSAGE] contains a message with detailed information about the result,
+     * already processed by the language file
+     *
+     * if $aResult[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED then this node contains
+     * an empty string
+     *
+     * The error messages themselves are stored in the language file. Additional variables are
+     * passed to the languages.inc.php function _t_ext() as an array and can be used there in the form of
+     * {0}, {1}, {2} ...
+     *
+     * Additional variables passed to the lang. file on errors (can be used in error messages):
+     *
+     * 	For all errors:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_ACTION]	= name of the action
+     * 		$arg0[CHECK_ACTION_LANG_FILE_MEMBERSHIP]= name of the current membership
+     *
+     * 	CHECK_ACTION_RESULT_LIMIT_REACHED:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_LIMIT]		= limit on number of actions allowed for the profile
+     * 		$arg0[CHECK_ACTION_LANG_FILE_PERIOD]	= period that the limit is set for (in hours, 0 if unlimited)
+     *
+     * 	CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_BEFORE]	= date/time since when the action is allowed
+     *
+     * 	CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER:
+     *
+     * 		$arg0[CHECK_ACTION_LANG_FILE_AFTER]		= date/time since when the action is not allowed
+     *
+     * $aResult[CHECK_ACTION_PARAMETER] contains an additional parameter that can be considered
+     * when performing the action (like the number of profiles to show in search result)
+    */
+    function checkAction($iProfileId, $iActionId, $bPerformAction = false)
+    {
+        $aResult = array();
+        $aLangFileParams = array();
+
+        $iProfileId = (int)$iProfileId;
+        $iActionId = (int)$iActionId;
+        $bPerformAction = $bPerformAction ? true : false;
+
+        $aMembership = $this->getMemberMembershipInfo($iProfileId); // get current profile's membership information
+
+        $aLangFileParams[CHECK_ACTION_LANG_FILE_MEMBERSHIP] = _t($aMembership['name']);
+        $aLangFileParams[CHECK_ACTION_LANG_FILE_SITE_EMAIL] = getParam('site_email');
+
+        $aAction = $this->oDb->getAction($aMembership['id'], $iActionId);
+        if (!$aAction)
+            bx_trigger_error('Unknown action ID: ' . $iActionId, 2);
+
+        $aResult[CHECK_ACTION_PARAMETER] = $aAction['additional_param_value'];
+        $aLangFileParams[CHECK_ACTION_LANG_FILE_ACTION] = !empty($aAction['title']) ? _t($aAction['title']) : $aAction['name'];
+
+        /**
+         * Action is not allowed for the current membership
+         */
+        if (is_null($aAction['id'])) {
+
+            $sLangKey = CHECK_ACTION_MESSAGE_NOT_ALLOWED;
+            if (isset($this->_aLevel2MessageMap[$aMembership['id']]))
+                $sLangKey = $this->_aLevel2MessageMap[$aMembership['id']];
+
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED;
+            $aResult[CHECK_ACTION_MESSAGE] = _t_ext($sLangKey, $aLangFileParams);
+            return $aResult;
+        }
+
+        /**
+         * Check fixed period limitations if present (also for non-members)
+         */
+        if($aAction['allowed_period_start'] && time() < $aAction['allowed_period_start']) {
+            $aLangFileParams[CHECK_ACTION_LANG_FILE_BEFORE] = bx_time_js($aAction['allowed_period_start'], BX_FORMAT_DATE_TIME);
+
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_BEFORE;
+            $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_BEFORE, $aLangFileParams);
+
+            return $aResult;
+        }
+
+        if($aAction['allowed_period_end'] && time() > $aAction['allowed_period_end']) {
+            $aLangFileParams[CHECK_ACTION_LANG_FILE_AFTER] = bx_time_js($aAction['allowed_period_end'], BX_FORMAT_DATE_TIME);
+
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_NOT_ALLOWED_AFTER;
+            $aResult[CHECK_ACTION_MESSAGE] = _t_ext(CHECK_ACTION_MESSAGE_NOT_ALLOWED_AFTER, $aLangFileParams);
+
+            return $aResult;
+        }
+
+        /**
+         * if non-member, allow action without performing further checks
+         */
+        if ($aMembership['id'] == MEMBERSHIP_ID_NON_MEMBER) {
+            $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+            return $aResult;
+        }
+
+        /**
+         * Check other limitations (for members only)
+         */
+        $iAllowedCnt = (int)$aAction['allowed_count']; ///< Number of allowed actions. Unlimited if not specified or 0
+        $iPeriodLen = (int)$aAction['allowed_period_len']; ///< Period for AllowedCount in hours. If not specified, AllowedCount is treated as total number of actions permitted.
+
+        if($iAllowedCnt > 0) {
+            $aActionTrack = $this->oDb->getActionTrack($iActionId, $iProfileId);
+
+            $iActionsLeft = $bPerformAction ? $iAllowedCnt - 1 : $iAllowedCnt;
+            $iValidSince = time();
+
+            /**
+             * Member is requesting/performing this action for the first time,
+             * and there is no corresponding record in sys_acl_actions_track table.
+             */
+            if(!$aActionTrack) {
+                $this->oDb->insertActionTarck($iActionId, $iProfileId, $iActionsLeft, $iValidSince);
+
+                $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+                return $aResult;
+            }
+
+            /**
+             * Action has been requested/performed at least once at this point and there is a corresponding record in sys_acl_actions_track table
+             *
+             * Action record in sys_acl_actions_track table is out of date.
+             */
+            $iPeriodEnd = (int)$aActionTrack['valid_since'] + $iPeriodLen * 3600; //ValidSince is in seconds, PeriodLen is in hours
+            if($iPeriodLen > 0 && $iPeriodEnd < time()) {
+                $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft, $iValidSince);
+
+                $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+                return $aResult;
+            }
+
+            $iActionsLeft = (int)$aActionTrack['actions_left']; ///< Action record is up to date
+
+            /**
+             * Action limit reached for now
+             */
+            if($iActionsLeft <= 0){
+                $aLangFileParams[CHECK_ACTION_LANG_FILE_LIMIT] = $iAllowedCnt;
+                $aLangFileParams[CHECK_ACTION_LANG_FILE_PERIOD] = $iPeriodLen;
+
+                $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_LIMIT_REACHED;
+                $aResult[CHECK_ACTION_MESSAGE] = '<div class="bx-acl-err-msg">' . _t_ext(CHECK_ACTION_MESSAGE_LIMIT_REACHED, $aLangFileParams) . ($iPeriodLen > 0 ? _t_ext(CHECK_ACTION_MESSAGE_MESSAGE_EVERY_PERIOD, $aLangFileParams) : '') . '.</div>';
+
+                return $aResult;
+            }
+
+            if($bPerformAction) {
+                $iActionsLeft--;
+                $this->oDb->updateActionTrack($iActionId, $iProfileId, $iActionsLeft);
+            }
+        }
+
+        $aResult[CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+        return $aResult;
+    }
+
+    /**
+     * Get the list of existing memberships
+     *
+     * @param  bool   $bPurchasableOnly if true, fetches only purchasable memberships; 'purchasable' here means that:
+     *                                  - MemLevels.Purchasable = 'yes'
+     *                                  - MemLevels.Active = 'yes'
+     *                                  - there is at least one pricing option for the membership
+     * @return array( membershipID_1 => membershipName_1,  membershipID_2 => membershipName_2, ...) if no such memberships, then just array()
+     */
+    function getMemberships($bPurchasableOnly = false, $bActiveOnly = false, $isTranslate = true, $bFilterOutSystemAutomaticLevels = false)
+    {
+        $sType = 'all_pair';
+        if($bPurchasableOnly)
+            $sType = 'all_active_purchasble_pair';
+        else if($bActiveOnly)
+            $sType = 'all_active_pair';
+
+        $aLevels = array();
+        $this->oDb->getLevels(array('type' => $sType), $aLevels, false);
+        if ($isTranslate)
+            foreach ($aLevels as $k => $s)
+                $aLevels[$k] = _t($s);
+
+        if ($bFilterOutSystemAutomaticLevels) {
+            unset($aLevels[MEMBERSHIP_ID_NON_MEMBER]);
+            unset($aLevels[MEMBERSHIP_ID_ACCOUNT]);
+            unset($aLevels[MEMBERSHIP_ID_UNCONFIRMED]);
+            unset($aLevels[MEMBERSHIP_ID_PENDING]);
+            unset($aLevels[MEMBERSHIP_ID_SUSPENDED]);
+        }
+
+        return $aLevels;
+    }
+
+    function getMembershipsBy($aParams)
+    {
+        $aLevels = array();
+        $this->oDb->getLevels($aParams, $aLevels, false);
+
+        return $aLevels;
+    }
+
+    /**
+     * Get info about a given membership
+     *
+     * @param  int    $iLevelId membership to get info about
+     * @return array(
+     *                         'id'					=>	ID,
+     *                         'name'					=>	name,
+     *                         'icon'					=>	icon,
+     *                         'description'			=>	description,
+     *                         'active'				=>	active,
+     *                         'purchasable'			=>	purchasable,
+     *                         'removable'				=>	removable
+     *                         'quota_size'			=>	quota size,
+     *                         'quota_number'			=>	quota number,
+     *                         'quota_max_file_size'	=>	quota max file size
+     *                         )
+     */
+    function getMembershipInfo($iLevelId)
+    {
+        $aLevel = array();
+        $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false);
+        return $aLevel;
+    }
+
+    /**
+     * Retrieves information about membership for a given profile at a given moment.
+     *
+     * If there are no memberships purchased/assigned to the
+     * given profile or all of them have expired at the given point,
+     * the profile is assumed to be a standard profile, and the function
+     * returns	information about the Standard membership. This will
+     * also happen if a profile wasnt actually registered in the database
+     * at that point - the function will still return info about Standard
+     * membership, not the Non-member one.
+     *
+     * If there is no profile with the given $iProfileId,
+     * the function returns information about the Non-member or Authenticated
+     * predefined membership.
+     *
+     * The Standard, Authenticated and Non-member memberships have their
+     * DateStarts and DateExpires attributes set to NULL.
+     *
+     * @param  int    $iProfileId ID of a profile to get info about
+     * @param  int    $time       specifies the time to use when determining membership; if not specified, the function takes the current time
+     * @return array(
+     *                           'id'			=> membership id,
+     *                           'name'			=> membership name,
+     *                           'date_starts'	=> (UNIX timestamp) date/time purchased,
+     *                           'date_expires'	=> (UNIX timestamp) date/time expires
+     *                           )
+     */
+    function getMemberMembershipInfo($iProfileId, $iTime = 0)
+    {
+        $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime);
+        if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']]))
+            return $aMembershipCurrent;
+
+        $aMembership = $aMembershipCurrent;
+        do {
+            $iDateStarts = $aMembership['date_starts'];
+            $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, ((int)$iDateStarts < 1 ? 0 : $iDateStarts - 1));
+        }
+        while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_starts']);
+
+        $aMembership = $aMembershipCurrent;
+        do {
+            $iDateExpires = $aMembership['date_expires'];
+            $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $iDateExpires);
+        } while($aMembership['id'] == $aMembershipCurrent['id'] && (int)$aMembership['date_expires']);
+
+        $aMembershipCurrent['date_starts'] = $iDateStarts;
+        $aMembershipCurrent['date_expires'] = $iDateExpires;
+
+        return $aMembershipCurrent;
+    }
+
+    /**
+     * Set a membership for a profile
+     *
+     * @param  int     $iProfileId profile that is going to get the membership
+     * @param  int     $iLevelId   membership that is going to be assigned to the profile
+     *                             if $iLevelId == MEMBERSHIP_ID_STANDARD then $days and $bStartsNow parameters are not used,
+     *                             so Standard membership is always set immediately and `forever`
+     * @param  mixed   $mixedPeriod  number of Days to set membership for or an array with 'period'-'period unit' pair. If number or 'period' in pair equal 0, then the membership is set forever
+     * @param  boolean $bStartsNow if true, the membership will start immediately if false, the membership will start after the current membership expires
+     * @return boolean true in case of success, false in case of failure
+     */
+    function setMembership($iProfileId, $iLevelId, $mixedPeriod = 0, $bStartsNow = false, $sTransactionId = '')
+    {
+        $iProfileId = (int)$iProfileId;
+        $iLevelId = (int)$iLevelId;
+        $bStartsNow = $bStartsNow ? true : false;
+
+        if (!$iProfileId)
+            $iProfileId = -1;
+
+        if (empty($sTransactionId))
+            $sTransactionId = 'NULL';
+
+        // check if profile exists
+        if(($sProfileEmail = BxDolProfileQuery::getInstance()->getEmailById($iProfileId)) === false)
+            return false;
+
+        // check if membership exists
+        $aLevel = array();
+        $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iLevelId), $aLevel, false);
+        if(empty($aLevel) || !is_array($aLevel))
+            return false;
+
+        if($iLevelId == MEMBERSHIP_ID_NON_MEMBER)
+            return false;
+
+        $aMembershipCurrent = $this->getMemberMembershipInfo($iProfileId);
+        $aMembershipLatest = $this->getMemberMembershipInfoLatest($iProfileId);
+
+        // setting Standard membership level
+        if ($iLevelId == MEMBERSHIP_ID_STANDARD) {
+            if ($aMembershipCurrent['id'] == MEMBERSHIP_ID_STANDARD)
+                return true;
+
+            // delete present and future memberships
+            return $this->oDb->deleteLevelByProfileId($iProfileId);
+        }
+
+        if ((is_numeric($mixedPeriod) && (int)$mixedPeriod < 0) || (is_array($mixedPeriod) && (!isset($mixedPeriod['period']) || $mixedPeriod['period'] < 0)))
+            return false;
+
+        $iDateStarts = time();
+        if (!$bStartsNow) {
+            // Make the membership starts after the latest membership expires or return false if latest membership isn't Standard and is lifetime membership.
+            if(!empty($aMembershipLatest['date_expires']))
+                $iDateStarts = $aMembershipLatest['date_expires'];
+            else if(empty($aMembershipLatest['date_expires']) && $aMembershipLatest['id'] != MEMBERSHIP_ID_STANDARD)
+                return false;
+        }
+        else {
+            // Delete any profile's membership level and actions traces
+            $this->oDb->deleteLevelByProfileId($iProfileId, true); 
+            $this->oDb->clearActionsTracksForMember($iProfileId);
+        }
+
+        // set lifetime membership if 0 days is used.
+        if(is_numeric($mixedPeriod))
+        	$mixedPeriod = array('period' => (int)$mixedPeriod, 'period_unit' => MEMBERSHIP_PERIOD_UNIT_DAY);
+
+        if(!$this->oDb->insertLevelByProfileId($iProfileId, $iLevelId, $iDateStarts, $mixedPeriod, $sTransactionId))
+           return false;
+
+        // raise membership alert
+        $oZ = new BxDolAlerts('profile', 'set_membership', '', $iProfileId, array('mlevel'=> $iLevelId, 'period' => $mixedPeriod['period'], 'period_unit' => $mixedPeriod['period_unit'], 'starts_now' => $bStartsNow, 'txn_id' => $sTransactionId));
+        $oZ->alert();
+
+        // Send notification
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemChanged', array('membership_level' => _t($aLevel['name'])), 0, $iProfileId);
+        if ($aTemplate)
+            sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body']);
+
+        return true;
+    }
+
+    function unsetMembership($iProfileId, $iLevelId, $sTransactionId)
+    {
+    	return $this->oDb->deleteLevelBy(array(
+    		'IDMember' => $iProfileId,
+    		'IDLevel' => $iLevelId,
+    		'TransactionID' => $sTransactionId
+    	));
+    }
+
+    /**
+     * get action id by module and name
+     * @param $sAction action name
+     * @param $sModule module name
+     * @param $aActions array of actions from sys_acl_actions table, with default array keys (starting from 0) and text values
+     */
+    function getMembershipActionId($sAction, $sModule)
+    {
+        $this->oDb->getActions(array('type' => 'by_names_and_module', 'value' => $sAction, 'module' => $sModule), $aActions, false);
+        if (count($aActions) > 1)
+            trigger_error('Duplicate action - name:' . $sAction . ', module:' . $sModule, E_USER_ERROR);
+        $aAction = array_pop($aActions);
+        return $aAction['id'];
+    }
+
+    function getExpirationLetter($iProfileId, $sLevelName, $iLevelExpireDays )
+    {
+        $iProfileId = (int)$iProfileId;
+
+        if(!$iProfileId)
+            return false;
+
+        $oProfileQuery = BxDolProfileQuery::getInstance();
+        $sProfileEmail = $oProfileQuery->getEmailById($iProfileId);
+
+        $aPlus = array(
+            'membership_name' => $sLevelName,
+            'expire_days' => $iLevelExpireDays,
+            'page_url' => BxDolRequest::serviceExists('bx_acl', 'get_view_url') ? BxDolService::call('bx_acl', 'get_view_url') : '#'
+        );
+
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_MemExpiration', $aPlus, 0, $iProfileId);
+
+        $iResult = $aTemplate && sendMail($sProfileEmail, $aTemplate['Subject'], $aTemplate['Body'], $iProfileId, $aPlus);
+        return !empty($iResult);
+    }
+
+    /**
+     * clear expired membership levels
+     */
+    public function maintenance()
+    {
+        return $this->oDb->maintenance();
+    }
+
+    protected function getMemberMembershipInfoCurrent($iProfileId, $iTime = 0)
+    {
+        $aMemLevel = false;
+
+        // get profile status
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        $aProfileInfo = $oProfile ? $oProfile->getInfo() : false;
+        $sProfileStatus = $aProfileInfo ? $aProfileInfo['status'] : false;
+        $sProfileType = $aProfileInfo ? $aProfileInfo['type'] : false;
+
+        // account profile
+	    if($sProfileType == 'system') {
+        	$aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_ACCOUNT);
+        	if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: MEMBERSHIP_ID_ACCOUNT', E_USER_ERROR);
+
+        	return $aMemLevel;
+        }
+
+        // check if account is unconfirmed, every account's profile is unconfirmed if account is unconfirmed
+        $oAccount = $aProfileInfo ? BxDolAccount::getInstance($aProfileInfo['account_id']) : false;
+        if ($oAccount && !$oAccount->isConfirmed()) {
+            $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_UNCONFIRMED);
+            if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: MEMBERSHIP_ID_UNCONFIRMED', E_USER_ERROR);
+
+            return $aMemLevel;
+        }
+        
+        // profile is not active, so return standard memberships according to profile status        
+        if (false === $sProfileStatus || BX_PROFILE_STATUS_ACTIVE != $sProfileStatus) {                
+            if (!isset($this->_aProfileStatus2LevelMap[$sProfileStatus]))
+                $iLevelId = MEMBERSHIP_ID_NON_MEMBER; // if there is no profile status - then it isn't member
+            else
+                $iLevelId = $this->_aProfileStatus2LevelMap[$sProfileStatus]; // get member level id which associated with every non-active status
+
+            $aMemLevel = $this->oDb->getLevelByIdCached($iLevelId);
+
+            if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: ' . $iLevelId, E_USER_ERROR);
+
+            return $aMemLevel;
+        }
+
+        // profile is active get memebr level from profile
+        $aMemLevel = $this->oDb->getLevelCurrent((int)$iProfileId, $iTime);
+
+        // There are no purchased/assigned memberships for the profile or all of them have expired.
+        // In this case the profile is assumed to have Standard membership.
+        if (!$aMemLevel || is_null($aMemLevel['id'])) {
+            $aMemLevel = $this->oDb->getLevelByIdCached(MEMBERSHIP_ID_STANDARD);
+            if (!$aMemLevel)
+                trigger_error ('Standard member level is missing: ' . MEMBERSHIP_ID_NON_MEMBER, E_USER_ERROR);
+        }
+
+        return $aMemLevel;
+    }
+
+    protected function getMemberMembershipInfoLatest($iProfileId, $iTime = 0)
+    {
+        $aMembershipCurrent = $this->getMemberMembershipInfoCurrent($iProfileId, $iTime);
+        if (isset($this->_aStandardMemberships[$aMembershipCurrent['id']]))
+            return $aMembershipCurrent;
+
+        $aMembership = $aMembershipCurrent;
+        while ($aMembership['id'] != MEMBERSHIP_ID_STANDARD) {
+            $aMembershipLast = $aMembership;
+            if(!isset($aMembership['date_expires']) || (int)$aMembership['date_expires'] == 0)
+                break;
+
+            $aMembership = $this->getMemberMembershipInfoCurrent($iProfileId, $aMembership['date_expires']);
+        }
+
+        return $aMembershipLast;
+    }
+
+    public function onProfileDelete ($iProfileId)
+    {
+        return $this->oDb->deleteLevelByProfileId($iProfileId, true);
+    }
+}
+
+function checkAction($iProfileId, $iActionId, $bPerformAction = false)
+{
+    return BxDolAcl::getInstance()->checkAction($iProfileId, $iActionId, $bPerformAction);
+}
+
+function checkActionModule($iProfileId, $sActionName, $sModuleName, $bPerformAction = false)
+{
+    $oACL = BxDolAcl::getInstance();
+
+    $iActionId = $oACL->getMembershipActionId($sActionName, $sModuleName);
+
+    return $oACL->checkAction($iProfileId, $iActionId, $bPerformAction);
+}
+
+/** @} */

+ 1009 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/classes/BxDolSearch.php

@@ -0,0 +1,1009 @@
+<?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
+ * @{
+ */
+
+define('BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT', 10);
+define('BX_DOL_SEARCH_KEYWORD_PAGE', 'site-search-page');
+
+/**
+ * Search class for different content.
+ *
+ * Allows present content in main search area for all modules.
+ *
+ * To add taking part content from your module to search area you need add a record to `sys_objects_search` table:
+ * @code
+ *   ID - autoincremented id for internal usage
+ *   ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ *   Title - language key for module's choicer in global search form
+ *   ClassName - class name which is responsible for search process ad showing result for found results
+ *   ClassPath - file where your ClassName is stored (if class is stored in template folder then you can use key {tmpl})
+ * @endcode
+ *
+ * For using this class you should have unit of BxDolSearchResult class (see description below) with method processing which is responsible for processing of search request.
+ *
+ * Example of usage can be seen in the BoonEx modules, for example: Posts
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+class BxDolSearch extends BxDol
+{
+    protected $aClasses = array(); ///< array of all search classes
+    protected $aChoice  = array(); ///< array of current search classes which were choosen in search area
+    protected $_bRawProcessing = false; ///< display search results without design box and paginate
+
+    protected $_bLiveSearch = false;
+    protected $_sMetaType = '';
+    protected $_sCategoryObject = '';
+    protected $_aCustomSearchCondition = array();
+    protected $_aCustomCurrentCondition = array();
+    protected $_sUnitTemplate = '';
+
+    /**
+     * Constructor
+     * @param array $aChoice - array of choosen classes (will take a part only existing in `sys_objects_search` table)
+     */
+    public function __construct ($aChoice = '')
+    {
+        parent::__construct();
+
+        $this->aClasses = BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
+           'SELECT `ID` as `id`,
+                   `Title` as `title`,
+                   `ClassName` as `class`,
+                   `ClassPath` as `file`,
+                   `ObjectName`,
+                   `GlobalSearch`
+            FROM `sys_objects_search`
+            ORDER BY `Order` ASC', 'ObjectName'
+        );
+
+        if (is_array($aChoice) && !empty($aChoice)) {
+            foreach ($aChoice as $sValue) {
+                if (isset($this->aClasses[$sValue]))
+                    $this->aChoice[$sValue] = $this->aClasses[$sValue];
+            }
+        } else {
+            $this->aChoice = $this->aClasses;
+        }
+    }
+
+    /**
+     * create units for all classes and calling their processing methods
+     */
+    public function response ()
+    {
+        $sCode = '';
+
+        $bSingle = count($this->aChoice) == 1;
+        foreach ($this->aChoice as $sKey => $aValue) {
+            if (!$this->_sMetaType && !$aValue['GlobalSearch'])
+                continue;
+
+        	$sClassName = 'BxTemplSearchResult';
+	        if(!empty($aValue['class'])) {
+	            $sClassName = $aValue['class'];
+	            if(!empty($aValue['file']))
+	                require_once(BX_DIRECTORY_PATH_ROOT . $aValue['file']);
+	        }
+
+            $oEx = new $sClassName();            
+            if ($this->_sMetaType && !$oEx->isMetaEnabled($this->_sMetaType))
+                continue;
+            $oEx->setId($aValue['id']);
+            $oEx->setLiveSearch($this->_bLiveSearch);
+            $oEx->setMetaType($this->_sMetaType);
+            $oEx->setCategoryObject($this->_sCategoryObject);
+            $oEx->setCenterContentUnitSelector(false);
+            $oEx->setSingleSearch($bSingle);
+            $oEx->setCustomSearchCondition($this->_aCustomSearchCondition);
+            $oEx->aCurrent = array_merge($oEx->aCurrent, $this->_aCustomCurrentCondition);
+            if ($this->_sUnitTemplate)
+                $oEx->setUnitTemplate($this->_sUnitTemplate);
+            $sCode .= $this->_bRawProcessing ? $oEx->processingRaw() : $oEx->processing();
+        }
+
+        return $sCode;
+    }
+
+    public function getEmptyResult ()
+    {
+        $sKey = _t('_Empty');
+        return DesignBoxContent($sKey, MsgBox($sKey), 1);
+    }
+
+    protected function getKeyTitlesPairs ()
+    {
+        $a = array();
+        foreach ($this->aChoice as $sKey => $r)
+            if ($this->_sMetaType || $r['GlobalSearch'])
+                $a[$sKey] = _t($r['title']);
+        return $a;
+    }
+
+    public function setLiveSearch($bLiveSearch)
+    {
+        $this->_bLiveSearch = $bLiveSearch;
+    }
+
+    public function setMetaType($s)
+    {
+        $aMetaTypes = array('location_country', 'location_country_city', 'location_country_state', 'mention', 'keyword');
+        if (in_array($s, $aMetaTypes))
+            $this->_sMetaType = $s;
+    }
+    
+    public function setCategoryObject($s)
+    {
+        $this->_sCategoryObject = $s;
+    }
+
+    /**
+     * Set custom search condition to use istead of GET/POST variables
+     * @param $a array of params, such as 'keyword', 'state', 'city'
+     * @return nothing
+     */
+    public function setCustomSearchCondition($a)
+    {
+        $this->_aCustomSearchCondition = $a;
+    }
+
+    /**
+     * Set custom data for aCurrent array
+     * @param $a array of params
+     * @return nothing
+     */
+    public function setCustomCurrentCondition($a)
+    {
+        $this->_aCustomCurrentCondition = $a;
+    }
+
+    /**
+     * Set custom unit template
+     * @param $s template name
+     * @return nothing
+     */
+    public function setUnitTemplate($s)
+    {
+        $this->_sUnitTemplate = $s;
+    }
+    
+    /**
+     * Display search results without design box and paginate
+     */ 
+    public function setRawProcessing($b)
+    {
+        $this->_bRawProcessing = $b;
+    }
+}
+
+/*
+ * Search class for processing search requests and displaying search results.
+ *
+ * Allows present content from modules on search params or internal (via fields of class) conditions.
+ *
+ * Example of usage (you can see example in any of default Dolhpin's modules):
+ *
+ * 1. Extends your own search class from this one (or from BxBaseSearchResult or BxBaseSearchResultSharedMedia classes)
+ * 2. Set necessary fields of class (using as example BxFilesSearch):
+ *
+ * @code
+ *
+ *  // main field of search class
+ *  $this->aCurrent = array(
+ *
+ *      // name of module
+ *      'name' => 'bx_files',
+ *
+ *      // language key with name of module
+ *      'title' => '_bx_files',
+ *
+ *      // main content table
+ *      'table' => 'bx_files_main',
+ *
+ *      // array of all fields which can be choosen for select in result query
+ *      'ownFields' => array('ID', 'Title', 'Uri', 'Desc', 'Date', 'Size', 'Ext', 'Views', 'Rate', 'RateCount', 'Type'),
+ *
+ *      // array of fields which take a part in search by keyword (global search and related words only - in other cases leave it blank)
+ *      'searchFields' => array('Title', 'Tags', 'Desc', 'Categories'),
+ *
+ *      // array of join tables
+ *      //   'type' - type of join
+ *      //   'table' - join table
+ *      //   'mainField' - field from main table for 'on' condition
+ *      //   'onField' - field from joining table for 'on' condition
+ *      //   'joinFields' - array of fields from joining table
+ *
+ *      'join' => array(
+ *          'profile' => array(
+ *              'type' => 'left',
+ *              'table' => 'Profiles',
+ *              'mainField' => 'Owner',
+ *              'onField' => 'ID',
+ *              'joinFields' => array('NickName'),
+ *          ),
+ *          // ...
+ *      ),
+ *
+ *      // array of search parameters
+ *      //   'value' - value of search parameter (can be number, string or array)
+ *      //   'field' - field which will take value of search, may be lefy as blank then in search query will be pasted to WHERE conditon via operator AGAINST
+ *      //   'operator' - operator between field and value (can be 'in', 'not in', '>', '<', 'against', 'like' and '=' by default),
+ *      //   'paramName' - GET param which will keep 'value' for pagianation
+ *
+ *      'restriction' => array(
+ *          'activeStatus' => array('value'=>'approved', 'field'=>'Status', 'operator'=>'=', 'paramName' => 'status'),
+ *          'albumType' => array('value'=>'', 'field'=>'Type', 'operator'=>'=', 'paramName'=>'albumType', 'table'=>'sys_albums'),
+ *      ),
+ *
+ *      // array of pagination
+ *      //   'perPage' - units per page
+ *      //   'start' - show search results starting from this number
+ *      //   'num' - number of element to show on current page + 1
+ *
+ *      'paginate' => array('perPage' => 10, 'start' => 0, 'num' => 11),
+ *
+ *      // sort mode - by default it was last (DESC by date)
+ *      'sorting' => 'last',
+ *
+ *      // mode of units presentation in search result, can be 'short' or 'full' view
+ *      'view' => 'full',
+ *
+ *      // field of id
+ *      'ident' => 'ID',
+ *
+ *      // rss feed array
+ *      'rss' => array(
+ *          'title' => '',
+ *          'link' => '',
+ *          'image' => '',
+ *          'profile' => 0,
+ *          'fields' => array (
+ *              'Link' => '',
+ *              'Title' => 'Title',
+ *              'DateTimeUTS' => 'Date',
+ *              'Desc' => 'Desc',
+ *              'Photo' => '',
+ *          ),
+ *      ),
+ *  );
+ *
+ *  // array of fields renamings
+ *  $aPseud - filling in costructor by function _getPseud
+ *
+ *  // unique identificator from `sys_objects_search` table
+ *  $id;
+ *
+ * @endcode
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+
+class BxDolSearchResult implements iBxDolReplaceable
+{
+    public $aCurrent; ///< search results configuration
+
+    protected $aPseud; ///< array of fields renamings
+    protected $id; ///< unique identificator of block on mixed search results page
+    protected $bDisplayEmptyMsg = false; ///< display empty message instead of nothing, when no results
+    protected $sDisplayEmptyMsgKey = ''; ///< custom empty message language key, instead of default "empty" message
+    protected $bProcessPrivateContent = true; ///< check each item for privacy, if view isn't allowed then display private view instead
+    protected $bForceAjaxPaginate = false; ///< force ajax paginate
+    protected $iPaginatePerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT; ///< default 'per page' value for paginate.
+
+    protected $_bSingleSearch = true;
+    protected $_bLiveSearch = false;
+    protected $_sMetaType = '';
+    protected $_sCategoryObject = '';
+    protected $_aCustomSearchCondition = array();
+
+    protected $_aMarkers = array (); ///< markers to replace somewhere, usually title and browse url (defined in custom class)
+
+    /**
+     * constructor
+     * filling identificator field
+     */
+    function __construct ()
+    {
+        if (isset($this->aPseud['id']))
+            $this->aCurrent['ident'] = $this->aPseud['id'];
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setId($sId)
+    {
+        $this->id = $sId;
+    }
+
+    public function setAjaxPaginate($b = true)
+    {
+        $this->bForceAjaxPaginate = $b;
+    }
+
+    public function setPaginatePerPage($iPerPage)
+    {
+        $this->iPaginatePerPage = $iPerPage;
+    }
+
+    public function setSingleSearch($bSingleSearch)
+    {
+        $this->_bSingleSearch = $bSingleSearch;
+    }
+
+    public function setLiveSearch($bLiveSearch)
+    {
+        $this->_bLiveSearch = $bLiveSearch;
+    }
+
+    public function setMetaType($s)
+    {
+        $this->_sMetaType = $s;
+    }
+
+    public function isMetaEnabled($s)
+    {
+        if (empty($this->aCurrent['object_metatags']))
+            return false;
+            
+        if (!($o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags'])))
+            return false;
+
+        switch ($s) {
+            case 'location_country_city':
+            case 'location_country_state':            
+            case 'location_country':
+                return $o->locationsIsEnabled();
+            case 'mention':
+                return $o->mentionsIsEnabled();
+            case 'keyword':
+                return $o->keywordsIsEnabled();
+        }
+
+        return false;
+    }
+    
+    public function setCategoryObject($s)
+    {
+        $this->_sCategoryObject = $s;
+    }
+
+    public function setCustomSearchCondition($a)
+    {
+        $this->_aCustomSearchCondition = $a;
+    }
+    
+    /**
+     * Display empty message if there is no content, custom empty message can be used.
+     * @param $b - boolan value to enable or disable 'empty' message
+     * @param $sLangKey [optional] - custom 'empty' message
+     */
+    public function setDisplayEmptyMsg($b, $sLangKey = '')
+    {
+        $this->bDisplayEmptyMsg = $b;
+        if ($sLangKey)
+           $this->sDisplayEmptyMsgKey = $sLangKey;
+    }
+
+    /**
+     * Perform privacy checking for every unit
+     * @param $b - boolan value to enable or disable privacy checking
+     */
+    public function setProcessPrivateContent ($b)
+    {
+        $this->bProcessPrivateContent = $b;
+    }
+
+    /**
+     * Get search results without design box and paginate
+     * @return html code
+     */
+    function processingRaw ()
+    {
+        $sCode = $this->displayResultBlock();
+        return $this->aCurrent['paginate']['num'] > 0 ? $sCode : '';
+    }
+    
+    /**
+     * Get html box of search results (usually used in grlobal search)
+     * @return html code
+     */
+    function processing ()
+    {
+        $sCode = $this->displayResultBlock();
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $sPaginate = $this->showPagination();
+            $sCode = $this->displaySearchBox($sCode, $sPaginate);
+        } else {
+            $sCode = $this->bDisplayEmptyMsg ? $this->displaySearchBox(MsgBox(_t($this->sDisplayEmptyMsgKey ? $this->sDisplayEmptyMsgKey : '_Empty'))) : '';
+        }
+        return $sCode;
+    }
+
+    /**
+     * Get html output of search result
+     * @return html code
+     */
+    function displayResultBlock ()
+    {
+        $aData = $this->getSearchData();
+        if (count($aData) > 0) {
+            $sCode .= $this->addCustomParts();
+            foreach ($aData as $iKey => $aValue) {
+                $sCode .= $this->displaySearchUnit($aValue);
+            }
+        }
+        return $sCode;
+    }
+
+    /**
+     * Add different code before html output (usually redeclared)
+     * no return result
+     */
+    function addCustomParts ()
+    {
+
+    }
+
+    /**
+     * Get XML string for rss output
+     */
+    function rss ()
+    {
+        if (!isset($this->aCurrent['rss']['fields']) || !isset($this->aCurrent['rss']['link']))
+            return '';
+
+        $aData = $this->getSearchData();
+        $f = &$this->aCurrent['rss']['fields'];
+        if ($aData) {
+            foreach ($aData as $k => $a) {
+                $aData[$k][$f['Link']] = $this->getRssUnitLink ($a);
+
+                if(isset($f['Image']))
+                    $aData[$k][$f['Image']] = $this->getRssUnitImage ($a, $f['Image']);
+            }
+        }
+
+        $oRss = new BxDolRssFactory ();
+
+        return $oRss->GenRssByCustomData(
+            $aData,
+            isset($this->aCurrent['rss']['title']) && $this->aCurrent['rss']['title'] ? $this->aCurrent['rss']['title'] : $this->aCurrent['title'],
+            $this->aCurrent['rss']['link'],
+            $this->aCurrent['rss']['fields'],
+            isset($this->aCurrent['rss']['image']) ? $this->aCurrent['rss']['image'] : '',
+            isset($this->aCurrent['rss']['profile']) ? $this->aCurrent['rss']['profile'] : 0
+        );
+    }
+
+    /**
+     * Output RSS XML with XML header
+     */
+    function outputRSS ()
+    {
+        header('Content-Type: text/xml; charset=UTF-8');
+        echo $this->rss();
+    }
+
+    /**
+     * Return rss unit link (redeclared)
+     */
+    function getRssUnitLink (&$a)
+    {
+        // override this functions to return permalink to rss unit
+    }
+
+	/**
+     * Return rss unit image (redeclared)
+     */
+    function getRssUnitImage (&$a, $sField)
+    {
+        // override this functions to return image for rss unit
+    }
+
+    /**
+     * Naming fields in query's body
+     * @param string  $sFieldName  name of field
+     * @param string  $sTableName  name of field's table
+     *                             $param string $sOperator of field's calculation (like MAX)
+     * @param boolean $bRenameMode indicator for renaming and unsetting fields from field of class $this->aPseud
+     *                             return $sqlUnit sql code and unsetting elements from aPseud field
+     */
+    function setFieldUnit ($sFieldName, $sTableName, $sOperator = '', $bRenameMode = true)
+    {
+        if (!empty($sOperator))
+            $sqlUnit  = "$sOperator(`$sTableName`.`$sFieldName`)";
+        else
+            $sqlUnit  = "`$sTableName`.`$sFieldName`";
+
+        if (isset($this->aPseud) && $bRenameMode !== false) {
+            $sKey = array_search($sFieldName, $this->aPseud);
+            if ($sKey !== false) {
+                $sqlUnit .= " as `$sKey`";
+                unset($this->aPseud[$sKey]);
+            }
+        }
+        return $sqlUnit . ', ';
+    }
+
+    /**
+     * Get html code of of every search unit
+     * @param array $aData array of every search unit
+     *                     return html code
+     */
+    function displaySearchUnit ($aData)
+    {
+
+    }
+
+    /**
+     * Get html code of search box with search results
+     * @param string $sCode html code of search results
+     *                      $param $sPaginate html code of paginate
+     *                      return html code
+     */
+    function displaySearchBox ($sCode, $sPaginate = '')
+    {
+
+    }
+
+    /**
+     * Get html code of pagination
+     */
+    function showPagination ($bAdmin = false, $bChangePage = true, $bPageReload = true)
+    {
+
+    }
+
+    /**
+     * Get array of data with search results
+     * return array with data
+     */
+    function getSearchData ()
+    {
+        $this->aPseud = $this->_getPseud();
+        $this->setConditionParams();
+        if ($this->aCurrent['paginate']['num'] > 0) {
+            $aData = $this->getSearchDataByParams();
+            return $aData;
+        }
+        return array();
+    }
+
+    /**
+     * Get array with code for sql elements
+     * @param $bRenameMode indicator of renmaing fields
+     * return array with joinFields, ownFields, groupBy and join elements
+     */
+    function getJoins ($bRenameMode = true)
+    {
+        $aSql = array();
+        // joinFields & join
+        if (isset($this->aCurrent['join']) && is_array($this->aCurrent['join'])) {
+            $aSql = array('join'=>'', 'ownFields'=>'', 'joinFields'=>'', 'groupBy'=>'');
+            foreach ($this->aCurrent['join'] as $sKey => $aValue) {
+                $sAlias = isset($aValue['table_alias']) ? $aValue['table_alias'] : $aValue['table'];
+                $sTableAlias = isset($aValue['table_alias']) ? " AS {$aValue['table_alias']} " : '';
+
+                if (is_array($aValue['joinFields'])) {
+                    foreach ($aValue['joinFields'] as $sValue) {
+                        $aSql['joinFields'] .= $this->setFieldUnit($sValue, $sAlias, isset($aValue['operator']) ? $aValue['operator'] : null, $bRenameMode);
+                    }
+                }
+                // group by
+                if (isset($aValue['groupTable']))
+                    $aSql['groupBy'] =  "GROUP BY `{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
+                $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
+                $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON `{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`";
+                $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
+            }
+            $aSql['joinFields'] = trim($aSql['joinFields'], ', ');
+            $aSql['groupBy'] = trim($aSql['groupBy'], ', ');
+        }
+        return $aSql;
+    }
+
+    /**
+     * Concat sql parts of query, run it and return result array
+     * @param $aParams addon param
+     * return $aData multivariate array
+     */
+    function getSearchDataByParams ($aParams = '')
+    {
+        $aSql = array('ownFields'=>'', 'joinFields'=>'', 'order'=>'');
+
+        // searchFields
+        foreach ($this->aCurrent['ownFields'] as $sValue)
+            $aSql['ownFields'] .= $this->setFieldUnit($sValue, $this->aCurrent['table']);
+
+        // joinFields & join
+        $aJoins = $this->getJoins();
+        if (!empty($aJoins)) {
+            $aSql['ownFields'] .= $aJoins['ownFields'];
+            $aSql['ownFields'] .= $aJoins['joinFields'];
+            $aSql['join'] = $aJoins['join'];
+            $aSql['groupBy'] = $aJoins['groupBy'];
+        } 
+
+        $aSql['ownFields'] = trim($aSql['ownFields'], ', ');
+
+        // from
+        $aSql['from'] = " FROM `{$this->aCurrent['table']}`";
+
+        // where
+        $aSql['where'] = $this->getRestriction();
+
+        // limit
+        $aSql['limit'] = $this->getLimit();
+
+        // sorting
+        $this->setSorting();
+
+        $aSort = $this->getSorting($this->aCurrent['sorting']);
+        foreach ($aSort as $sKey => $sValue)
+            $aSql[$sKey] .= $sValue;
+
+        // execution
+        $sqlQuery = "SELECT " . $aSql['ownFields'];
+
+        $sqlQuery .= ' ' . $aSql['from'];
+
+        if (isset($aSql['join']))
+            $sqlQuery .= ' ' . $aSql['join'];
+
+        if (isset($aSql['where']))
+            $sqlQuery .= ' ' . $aSql['where'];
+
+        if (isset($aSql['groupBy']))
+            $sqlQuery .= ' ' . $aSql['groupBy'];
+
+        if (isset($aSql['order']))
+            $sqlQuery .= ' ' . $aSql['order'];
+
+        if (isset($aSql['limit']))
+            $sqlQuery .= ' ' . $aSql['limit'];
+
+        // echoDbg($sqlQuery);
+        $aRes = BxDolDb::getInstance()->getAll($sqlQuery);
+        return $aRes;
+    }
+
+    /**
+     * Set class fields condition params and paginate array
+     */
+    function setConditionParams()
+    {
+        // keyword
+        $sKeyword = bx_process_input(isset($this->_aCustomSearchCondition['keyword']) ? $this->_aCustomSearchCondition['keyword'] : bx_get('keyword'));
+        if ($sKeyword !== false) {
+            $this->aCurrent['restriction']['keyword'] = array(
+                'value' => $sKeyword,
+                'field' => '',
+                'operator' => 'against'
+            );
+        }
+
+        // owner
+        if (isset($_GET['ownerName'])) {
+            $sName = bx_process_input($_GET['ownerName']);
+            $iUser = (int)BxDolProfileQuery::getInstance()->getIdByNickname($sName);
+            BxDolMenu::getInstance()->setCurrentProfileID($iUser);
+        } elseif (isset($_GET['userID']))
+            $iUser = bx_process_input($_GET['userID'], BX_DATA_INT);
+
+        if (!empty($iUser))
+            $this->aCurrent['restriction']['owner']['value'] = $iUser;
+        
+        // meta info
+        if ($this->_sMetaType && !empty($this->aCurrent['object_metatags'])) {
+            $o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags']);
+            if ($o) {
+                unset($this->aCurrent['restriction']['keyword']);
+                switch ($this->_sMetaType) {
+                    case 'location_country':
+                        $o->locationsSetSearchCondition($this, $sKeyword);
+                        break;
+                    case 'location_country_state':
+                        $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')));
+                        break;
+                    case 'location_country_city':
+                        $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')), bx_process_input(isset($this->_aCustomSearchCondition['city']) ? $this->_aCustomSearchCondition['city'] : bx_get('city')));
+                        break;
+                    case 'mention':
+                        $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
+                        $o->mentionsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
+                        break;
+                    case 'keyword':
+                        $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
+                        $o->keywordsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
+                        break;
+                }
+            }
+        }
+
+        // category
+        if ($this->_sCategoryObject && ($o = BxDolCategory::getObjectInstance($this->_sCategoryObject)) && $this->_bSingleSearch) {
+            unset($this->aCurrent['restriction']['keyword']);
+            $o->setSearchCondition($this, $sKeyword);
+        }
+
+        $this->setPaginate();
+        $iNum = $this->getNum();
+        if ($iNum > 0) {
+            $this->aCurrent['paginate']['num'] = $iNum;
+        } else {
+           $this->aCurrent['paginate']['num'] = 0;
+        }
+    }
+
+    /**
+     * Check number of records on current page
+     * return number of records on current page + 1
+     */
+    function getNum ()
+    {
+        $aJoins = $this->getJoins(false);
+        $sqlQuery =  "SELECT * FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? $aJoins['join'] : '' ). $this->getRestriction() . (isset($aJoins['groupBy']) ? $aJoins['groupBy'] : '') . ' ' . $this->getLimit(true);
+        return count(BxDolDb::getInstance()->getAll($sqlQuery));
+    }
+
+    /**
+     * Check restriction params and make condition part of query
+     * return $sqlWhere sql code of query for WHERE part
+     */
+    function getRestriction ()
+    {
+        $oDb = BxDolDb::getInstance();
+        $sqlWhere = '';
+        if (isset($this->aCurrent['restriction'])) {
+            $aWhere[] = '1';
+            foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
+                $sqlCondition = '';
+                if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
+                   $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
+                   $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
+                   switch ($aValue['operator']) {
+                       case 'nothing':
+                            $sqlCondition = " 0 ";
+                            break;
+                       case 'against':
+                            $aCond = isset($aValue['field']) && strlen($aValue['field']) > 0 ? $aValue['field'] : $this->aCurrent['searchFields'];
+                            $sqlCondition = !empty($aCond) ? $this->getSearchFieldsCond($aCond, $aValue['value']) : "";
+                            break;
+                       case 'like':
+                            $sqlCondition .= "LIKE " . $oDb->escape('%' . $aValue['value'] . '%');
+                            break;
+                       case 'in':
+                       case 'not in':
+                            $sValuesString = $this->getMultiValues($aValue['value']);
+                            $sqlCondition .= strtoupper($aValue['operator']) . '(' . $sValuesString . ')';
+                            break;
+                       default:
+                               $sqlCondition .= $aValue['operator'] . (isset($aValue['no_quote_value']) && $aValue['no_quote_value'] ?  $aValue['value'] : $oDb->escape($aValue['value']));
+                       break;
+                    }
+                }
+                if (strlen($sqlCondition) > 0)
+                    $aWhere[] = $sqlCondition;
+            }
+            $sqlWhere .= "WHERE ". implode(' AND ', $aWhere) . (isset($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '');
+        }
+
+        return $sqlWhere;
+    }
+
+    /**
+     * Get limit part of query
+     * return $sqlFrom code for limit part pf query
+     */
+    function getLimit ($isAddPlusOne = false)
+    {
+        if (!isset($this->aCurrent['paginate']))
+            return;
+
+        $sqlFrom = (int)$this->aCurrent['paginate']['start'] > 0 ? (int)$this->aCurrent['paginate']['start'] : 0;
+        $sqlTo = $this->aCurrent['paginate']['perPage'] + ($isAddPlusOne ? 1 : 0);
+        if ($sqlTo > 0)
+            return 'LIMIT ' . $sqlFrom . ', ' . $sqlTo;
+    }
+
+    /**
+     * Set sorting field of class
+     */
+    function setSorting ()
+    {
+        $this->aCurrent['sorting'] = isset($_GET[$this->aCurrent['name'] . '_mode']) ? $_GET[$this->aCurrent['name'] . '_mode'] : $this->aCurrent['sorting'];
+    }
+
+    /**
+     * Get sorting part of query according current sorting mode
+     * @param string $sSortType sorting type
+     *                          return array with sql elements order and ownFields
+     */
+    function getSorting ($sSortType = 'last')
+    {
+        $aOverride = $this->getAlterOrder();
+        if (is_array($aOverride) && !empty($aOverride))
+            return $aOverride;
+
+       $aSql = array();
+       switch ($sSortType) {
+           case 'rand':
+                $aSql['order'] = "ORDER BY RAND()";
+                break;
+           case 'score':
+                if (is_array($this->aCurrent['restriction']['keyword'])) {
+                    $sPseud = '';
+                    if ('on' == getParam('useLikeOperator')) {
+                        $aSql['order'] = "ORDER BY `score` DESC";
+                        $sPseud = 'score';
+                    }
+                    $aSql['ownFields'] .= $this->getSearchFieldsCond($this->aCurrent['searchFields'], $this->aCurrent['restriction']['keyword']['value'], $sPseud) . ',';
+                }
+                break;
+           case 'none':
+                $aSql['order'] = "";
+                break;
+           default:
+                $aSql['order'] = "ORDER BY `date` DESC";
+        }
+        return $aSql;
+    }
+
+    /**
+     * Return own varaint for sorting (redeclare if necessary)
+     * return array of sql elements
+     */
+    function getAlterOrder ()
+    {
+        return array();
+    }
+
+    /**
+     * Set paginate fields of class according GET params 'start' and 'per_page'
+     * forcePage is need for setting most important number of current page
+     */
+    function setPaginate ()
+    {
+        $iPerPage = 0;
+
+        //--- Check in aCurrent (low priority).
+        if(isset($this->aCurrent['paginate']['perPage']) && (int)$this->aCurrent['paginate']['perPage'] != 0)
+            $iPerPage = (int)$this->aCurrent['paginate']['perPage'];
+
+        //--- Check in GET params (high priority).
+        if(isset($_GET['per_page']) && (int)$_GET['per_page'] != 0)
+            $iPerPage = (int)$_GET['per_page'];
+
+        //--- Trying to get from System settings.
+        if (empty($iPerPage))
+            $iPerPage = (int)getParam('sys_per_page_search_keyword_' . ($this->_bSingleSearch ? 'single' : 'plural'));
+
+        //--- Use default value in case of emergency.
+        if (empty($iPerPage))
+            $iPerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
+
+        $this->aCurrent['paginate']['perPage'] = $iPerPage;
+            
+        $this->aCurrent['paginate']['start'] = isset($this->aCurrent['paginate']['forceStart'])
+            ? (int)$this->aCurrent['paginate']['forceStart']
+            : (empty($_GET['start']) ? 0 : (int)$_GET['start']);
+
+        if ($this->aCurrent['paginate']['start'] < 0)
+            $this->aCurrent['paginate']['start'] = 0;
+    }
+
+    function unsetPaginate()
+    {
+    	unset($this->aCurrent['paginate']);
+    }
+
+    /**
+     * Get sql where condition for search fields
+     * @param array of search fields
+     * @param string $sKeyword keyword value for search
+     * @param string sPseud for setting new name for generated set of fields in query
+     *                         return sql code of WHERE part in query
+     */
+    function getSearchFieldsCond ($aFields, $sKeyword, $sPseud = '')
+    {
+        if (!$sKeyword)
+            return '';
+
+        $sTable = empty($this->aCurrent['tableSearch']) ? $this->aCurrent['table'] : $this->aCurrent['tableSearch'];
+
+        $oDb = BxDolDb::getInstance();
+
+        $bLike = getParam('useLikeOperator');
+
+        if (!is_array($aFields))
+            $aFields = array($aFields);
+
+        if ($bLike == 'on') {
+            $sKeyword = $oDb->escape('%' . preg_replace('/\s+/', '%', $sKeyword) . '%');
+
+            $sSqlWhere = '';
+            foreach ($aFields as $sValue)
+                $sSqlWhere .= "`{$sTable}`.`$sValue` LIKE  " . $sKeyword . " OR ";
+
+            $sSqlWhere = '(' . trim($sSqlWhere, 'OR ') . ')';
+
+        } else {
+        	$sKeyword = $oDb->escape($sKeyword);
+
+            $sSqlWhere = '';
+            foreach ($aFields as $sValue)
+                $sSqlWhere .= "`{$sTable}`.`$sValue`, ";
+
+            $sSqlWhere = trim($sSqlWhere, ', ');
+            $sSqlWhere = " MATCH({$sSqlWhere}) AGAINST (" . $sKeyword . ") ";
+
+            if (!empty($sPseud))
+                $sSqlWhere .= " AS `$sPseud` ";
+        }
+
+        return $sSqlWhere;
+    }
+
+    /**
+     * Get set from several values for 'in' and 'not in' operators
+     * @param $aValues array of values
+     * return sql code for field with operator IN (NOT IN)
+     */
+    function getMultiValues ($aValues)
+    {
+        $oDb = BxDolDb::getInstance();
+        return $oDb->implode_escape($aValues);
+    }
+
+    /**
+     * System method for filling aPseud array.
+     * Fill field aPseud for current class (if you will use own getSearchUnit methods then not necessary to redeclare).
+     */
+    function _getPseud ()
+    {
+
+    }
+
+    /**
+     * Add replace markers. Markers are replaced in titles and browse urls
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Replace provided markers in a string
+     * @param $mixed string or array to replace markers in
+     * @return string where all occured markers are replaced
+     */
+    protected function _replaceMarkers ($mixed)
+    {
+        return bx_replace_markers($mixed, $this->_aMarkers);
+    }
+}
+
+/** @} */

+ 1059 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/js/functions.js

@@ -0,0 +1,1059 @@
+/**
+ * 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');
+    	$('<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) {
+
+    if ('undefined' != typeof(mixedLoadingElement))
+        bx_loading($(mixedLoadingElement), true);
+
+    $.post(sUrlRoot + 'menu.php', {o:'sys_set_acl_level', 'profile_id': iProfileId, 'acl_level_id': iAclLevel}, function(data) {
+
+        if ('undefined' != typeof(mixedLoadingElement))
+            bx_loading($(mixedLoadingElement), false);
+
+        if (data.length) {
+            bx_alert(data);
+        } else if ($(mixedLoadingElement).hasClass('bx-popup-applied')) {
+            $(mixedLoadingElement).dolPopupHide().remove();
+        }
+    });
+}
+
+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 ('undefined' == typeof(sLang)) {
+        sLang = glBxTimeLang;
+    } else {
+        glBxTimeLang = sLang;
+    }
+
+    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) {
+    if ('undefined' != typeof(e) && 13 == e.keyCode) {
+        $(sFormSel).find('input[name=live_search]').val(0);
+        $(sFormSel).submit();
+        return false;
+    }
+
+    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 ('undefined' == typeof(sColor))
+        sColor = glBxAnimIconColor;
+    else
+       glBxAnimIconColor = sColor;
+
+    $('.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)
+{
+	$(document).dolPopupAlert({
+		message: sMessage,
+		onClickOk: fOnClickOk
+	});
+}
+
+function bx_confirm(sMessage, fOnClickYes, fOnClickNo)
+{
+	$(document).dolPopupConfirm({
+		message: sMessage,
+		onClickYes: fOnClickYes,
+		onClickNo: fOnClickNo
+	});
+}
+
+function bx_prompt(sMessage, sValue, fOnClickOk, fOnClickCancel)
+{
+	$(document).dolPopupPrompt({
+		message: sMessage,
+		value: sValue,
+		onClickOk: fOnClickOk,
+		onClickCancel: fOnClickCancel
+	});
+}
+
+function bx_get_scripts (aFiles, fCallback) 
+{
+    var 
+        iLength = aFiles.length,
+        aDeferreds = [],
+        iCounter = 0,
+        i = 0,
+        fHandler = function() {
+            if (iCounter++ >= (iLength-1)) {
+                fCallback && fCallback();
+            } 
+            else if ('undefined' === typeof(glBxLoadedScripts[aFiles[iCounter]])) {
+                glBxLoadedScripts[aFiles[iCounter]] = aFiles[iCounter];
+                $.bxGetCachedScript(aFiles[iCounter]).done(fHandler);
+            }
+        };
+
+    if ('undefined' === typeof(glBxLoadedScripts))
+        glBxLoadedScripts = {}; 
+    
+    $.bxGetCachedScript(aFiles[iCounter]).done(fHandler);
+}
+
+jQuery.bxGetCachedScript = function(sUrl, oOptions) 
+{ 
+    oOptions = $.extend(oOptions || {}, {
+        dataType: "script",
+        cache: true,
+        url: sUrl
+    });
+
+    return jQuery.ajax(oOptions);
+};
+
+/** @} */

+ 1721 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/inc/utils.inc.php

@@ -0,0 +1,1721 @@
+<?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_LINK_CLASS', 'bx-link'); ///< class to add to every link in user content
+
+define('BX_DATA_TEXT', 1); ///< regular text data type
+define('BX_DATA_TEXT_MULTILINE', 2); ///< regular multiline text data type
+define('BX_DATA_INT', 3); ///< integer data type
+define('BX_DATA_FLOAT', 4); ///< float data type
+define('BX_DATA_CHECKBOX', 5); ///< checkbox data type, 'on' or empty value
+define('BX_DATA_HTML', 6); ///< HTML data type
+define('BX_DATA_DATE', 7); ///< date data type stored as yyyy-mm-dd
+define('BX_DATA_DATETIME', 12); ///< date/time data type stored as yyyy-mm-dd hh:mm:ss
+define('BX_DATA_DATE_TS', 8); ///< date data type stored as unixtimestamp
+define('BX_DATA_DATETIME_TS', 9); ///< date/time data type stored as unixtimestamp
+define('BX_DATA_DATE_TS_UTC', 10); ///< date data type stored as unixtimestamp from UTC time
+define('BX_DATA_DATETIME_TS_UTC', 11); ///< date/time data type stored as unixtimestamp from UTC time
+
+define('BX_SLASHES_AUTO', 0);
+define('BX_SLASHES_ADD', 1);
+define('BX_SLASHES_STRIP', 2);
+define('BX_SLASHES_NO_ACTION', 3);
+
+define('BX_ESCAPE_STR_AUTO', 0); ///< turn apostropes and quote signs into html special chars, for use in @see bx_js_string and @see bx_html_attribute
+define('BX_ESCAPE_STR_APOS', 1); ///< escape apostrophes only, for js strings enclosed in apostrophes, for use in @see bx_js_string and @see bx_html_attribute
+define('BX_ESCAPE_STR_QUOTE', 2); ///< escape quotes only, for js strings enclosed in quotes, for use in @see bx_js_string and @see bx_html_attribute
+
+define('BX_EMAIL_SYSTEM', 0); ///< system email without unsubscribe link, like forgot password or email verification
+define('BX_EMAIL_NOTIFY', 1); ///< notification message, with unsubscribe link
+define('BX_EMAIL_MASS', 2); ///< mass email, one mesage send to manu users, with unsubscribe link
+
+define('BX_MAINTENANCE_FILE', '.bx_maintenance'); ///< file name to use as mantenance mode indicator
+
+/**
+ * The following two functions are needed to convert title to uri and back.
+ * It usefull when titles are used in URLs, like in Categories and Tags.
+ */
+function title2uri($sValue)
+{
+    return str_replace(
+        array('&', '/', '\\', '"', '+'),
+        array('[and]', '[slash]', '[backslash]', '[quote]', '[plus]'),
+        $sValue
+    );
+}
+function uri2title($sValue)
+{
+    return str_replace(
+        array('[and]', '[slash]', '[backslash]', '[quote]', '[plus]'),
+        array('&', '/', '\\', '"', '+'),
+        $sValue
+    );
+}
+
+/*
+ * functions for limiting maximal text length
+ */
+function strmaxtextlen($sInput, $iMaxLen = 60, $sEllipsisSign = '&#8230;')
+{
+    $sTail = '';
+    $s = trim(strip_tags($sInput));
+    if (mb_strlen($s) > $iMaxLen) {
+        $s = mb_substr($s, 0, $iMaxLen);
+        $sTail = $sEllipsisSign;
+    }
+    return htmlspecialchars_adv($s) . $sTail;
+}
+
+function html2txt($content, $tags = "")
+{
+    while($content != strip_tags($content, $tags)) {
+        $content = strip_tags($content, $tags);
+    }
+
+    return $content;
+}
+
+/**
+ * Functions to process user input.
+ * DON'T use to process data before passing to SQL query - use db prepare instead @see BxDolDb::prepare.
+ * It is ok to use bx_process_input and then db prepare.
+ * @param $mixedData data to process
+ * @param $iDataType how to handle data, possible valies:
+ *      @code
+ *          BX_DATA_INT - integer value
+ *          BX_DATA_FLOAT - float values
+ *          BX_DATA_CHECKBOX - 'on' or empty string
+ *          BX_DATA_TEXT - text data, single line (default)
+ *          BX_DATA_TEXT_MULTILINE - text data, multiple lines
+ *          BX_DATA_HTML - HTML data
+ *          BX_DATA_DATE - date data type stored as yyyy-mm-dd
+ *          BX_DATA_DATETIME - date/time data type stored as yyyy-mm-dd hh:mm:ss
+ *          BX_DATA_DATE_TS' -  date data type stored as unixtimestamp
+ *          BX_DATA_DATETIME_TS - date/time data type stored as unixtimestamp
+ *          BX_DATA_DATE_TS_UTC - date data type stored as unixtimestamp from UTC time
+ *          BX_DATA_DATETIME_TS_UTC - date/time data type stored as unixtimestamp from UTC time
+ *      @endcode
+ * @param $mixedParams optional parameters to pass for validation
+ * @param $isCheckMagicQuotes deprecated
+ * @return the filtered data, or FALSE if the filter fails.
+ */
+function bx_process_input ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false, $isCheckMagicQuotes = true)
+{
+    if (is_array($mixedData)) {
+        foreach ($mixedData as $k => $v)
+            $mixedData[$k] = bx_process_input($v, $iDataType, $mixedParams);
+        return $mixedData;
+    }
+
+    if (get_magic_quotes_gpc() && $isCheckMagicQuotes)
+        $mixedData = stripslashes($mixedData);
+
+    switch ($iDataType) {
+    case BX_DATA_INT:
+        return filter_var(trim($mixedData), FILTER_VALIDATE_INT);
+    case BX_DATA_FLOAT:
+        return filter_var(trim($mixedData), FILTER_VALIDATE_FLOAT);
+    case BX_DATA_CHECKBOX:
+        return 'on' == trim($mixedData) ? 'on' : '';
+
+    case BX_DATA_DATE:
+        // maybe consider using strtotime
+        $mixedData = trim($mixedData);
+        if (!preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $mixedData))
+            return false;
+        list($iYear, $iMonth, $iDay) = explode('-', $mixedData); // 1985-10-28
+        $iDay   = intval($iDay);
+        $iMonth = intval($iMonth);
+        $iYear  = intval($iYear);
+        return sprintf("%04d-%02d-%02d", $iYear, $iMonth, $iDay);
+    case BX_DATA_DATETIME:
+        // maybe consider using strtotime
+        $mixedData = trim($mixedData);
+        if (!preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+)#', $mixedData, $m))
+            return bx_process_input ($mixedData, BX_DATA_DATE, $mixedParams, $isCheckMagicQuotes);
+        $iDay   = intval($m[3]);
+        $iMonth = intval($m[2]);
+        $iYear  = intval($m[1]);
+        $iH = intval($m[4]);
+        $iM = intval($m[5]);
+        $iS = isset($m[6]) ? intval($m[6]) : 0;
+        return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $iYear, $iMonth, $iDay, $iH, $iM, $iS); // 1985-10-28 00:59:35
+    case BX_DATA_DATE_TS:
+    case BX_DATA_DATE_TS_UTC:
+        $mixedData = trim($mixedData);
+        if (!preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $mixedData))
+            return false;
+        list($iYear, $iMonth, $iDay) = explode('-', $mixedData);
+        $iDay   = intval($iDay);
+        $iMonth = intval($iMonth);
+        $iYear  = intval($iYear);
+        $sFunc = BX_DATA_DATE_TS_UTC == $iDataType ? 'gmmktime' : 'mktime';
+        $iRet = $sFunc (0, 0, 0, $iMonth, $iDay, $iYear);
+        return $iRet > 0 ? $iRet : false;
+    case BX_DATA_DATETIME_TS:
+    case BX_DATA_DATETIME_TS_UTC:
+        if (!preg_match('#(\d+)\-(\d+)\-(\d+)[\s]{1}(\d+):(\d+):(\d+)[\s]{1}([+\-\d:Z]+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+)#', $mixedData, $m))
+            return bx_process_input ($mixedData, BX_DATA_DATETIME_TS == $iDataType ? BX_DATA_DATE_TS : BX_DATA_DATE_TS_UTC, $mixedParams, $isCheckMagicQuotes);
+        $iDay   = $m[3];
+        $iMonth = $m[2];
+        $iYear  = $m[1];
+        $iH = $m[4];
+        $iM = $m[5];
+        $iS = isset($m[6]) ? $m[6] : 0;        
+        $iTimezoneOffset = 0;
+        if (isset($m[7])) {
+            $oTz = new DateTimeZone($m[7]);
+            $oUtc = new DateTime(str_replace($m[7], '', $mixedData), new DateTimeZone('UTC'));
+            if ($oTz && $oUtc)
+                $iTimezoneOffset = $oTz->getOffset($oUtc);
+        }
+        $sFunc = BX_DATA_DATETIME_TS_UTC == $iDataType ? 'gmmktime' : 'mktime';
+        $iRet = $sFunc ($iH, $iM, $iS, $iMonth, $iDay, $iYear) - $iTimezoneOffset;
+
+        return $iRet > 0 ? $iRet : false;
+
+    case BX_DATA_HTML:
+        return clear_xss($mixedData);
+    case BX_DATA_TEXT_MULTILINE:
+        return nl2br(htmlspecialchars_adv($mixedData));
+    case BX_DATA_TEXT:
+    default:
+        return $mixedData;
+    }
+}
+
+/*
+ * Functions to process user output.
+ * Always use this function before output data which was entered by user before.
+ * @param $mixedData string to process
+ * @param $iDataType how to handle data, possible valies the same as in bx_process_input function, see bx_process_input.
+ * @param $mixedParams optional parameters to pass for validation
+ * @return the filtered data, or FALSE if the filter fails.
+ */
+function bx_process_output ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false)
+{
+    if (is_array($mixedData)) {
+        foreach ($mixedData as $k => $v)
+            $mixedData[$k] = bx_process_output($v, $iDataType, $mixedParams);
+        return $mixedData;
+    }
+
+    switch ($iDataType) {
+    case BX_DATA_INT:
+        return filter_var($mixedData, FILTER_VALIDATE_INT);
+    case BX_DATA_FLOAT:
+        return filter_var($mixedData, FILTER_VALIDATE_FLOAT);
+    case BX_DATA_CHECKBOX:
+        return 'on' == trim($mixedData) ? 'on' : '';
+
+    case BX_DATA_DATE:
+    case BX_DATA_DATETIME:
+        return $mixedData;
+    case BX_DATA_DATE_TS:
+        return empty($mixedData) ? '' : date("Y-m-d", (int)$mixedData);
+    case BX_DATA_DATE_TS_UTC:
+        return empty($mixedData) ? '' : gmdate("Y-m-d", (int)$mixedData);
+    case BX_DATA_DATETIME_TS:
+        return empty($mixedData) ? '' : date("Y-m-d H:i", (int)$mixedData);
+    case BX_DATA_DATETIME_TS_UTC:
+        return empty($mixedData) ? '' : gmdate("Y-m-d H:i", (int)$mixedData);
+
+    case BX_DATA_HTML:
+        return bx_linkify_html($mixedData, 'class="' . BX_DOL_LINK_CLASS . '"');
+    case BX_DATA_TEXT_MULTILINE:
+        return $mixedData;
+    case BX_DATA_TEXT:
+    default:
+        return htmlspecialchars_adv($mixedData);
+    }
+}
+
+/*
+ * This function apply bx_process_input and then bx_process_output.
+ * Use this function to output data immediately after receiving, without saving to database.
+ * Patams are the same as bx_process_input function - @see bx_process_input
+ */
+function bx_process_pass ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false, $isCheckMagicQuotes = true)
+{
+    return bx_process_output(bx_process_input ($mixedData, $iDataType, $mixedParams, $isCheckMagicQuotes), $iDataType, $mixedParams);
+}
+
+/**
+ * DEPRECATED
+ * use bx_process_input + bx_process_output instead
+ * --------
+ * function for processing pass data
+ *
+ * This function cleans the GET/POST/COOKIE data if magic_quotes_gpc() is on
+ * for data which should be outputed immediately after submit
+ */
+/*
+function process_pass_data( $text, $strip_tags = 0 )
+{
+    if ( $strip_tags )
+        $text = strip_tags($text);
+
+    if ( !get_magic_quotes_gpc() )
+        return $text;
+    else
+        return stripslashes($text);
+}
+*/
+
+/*
+ * function for output data from database into html
+ */
+function htmlspecialchars_adv( $string )
+{
+    return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
+}
+
+/**
+ * Send mail to user by parsing email template
+ */
+function sendMailTemplate($sTemplateName, $iAccountId = 0, $iProfileId = 0, $aReplaceVars = array(), $iEmailType = BX_EMAIL_NOTIFY, $bAddToQueue = false)
+{
+    $oProfile = BxDolProfile::getInstance($iProfileId);
+
+    $oAccount = $iAccountId ? BxDolAccount::getInstance($iAccountId) : ($oProfile ? $oProfile->getAccountObject() : null);
+
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oAccount || !$oProfile || !$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars, $oAccount ? $oAccount->id() : 0, (int)$iProfileId);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail($oAccount->getEmail(), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType, 'html', false, array(), $bAddToQueue);
+}
+
+/**
+ * Send system email 
+ */
+function sendMailTemplateSystem($sTemplateName, $aReplaceVars = array(), $iEmailType = BX_EMAIL_SYSTEM, $bAddToQueue = false)
+{
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType, 'html', false, array(), $bAddToQueue);
+}
+/**
+ * Send email function
+ *
+ * @param $sRecipientEmail - Email where email should be send
+ * @param $sMailSubject - subject of the message
+ * @param $sMailBody - Body of the message
+ * @param $iRecipientID - ID of recipient profile
+ * @param $aPlus - Array of additional information
+ * @param $iEmailType - email message type: BX_EMAIL_SYSTEM, BX_EMAIL_NOTIFY or BX_EMAIL_MASS
+ * @param $sEmailFlag - use 'html' for HTML email message
+ * @param $isDisableAlert - disable alert
+ * @param $aCustomHeaders - custom email headers
+ * @param $bAddToQueue - add message to email queue
+ * @return true if message was send or false otherwise
+ */
+function sendMail($sRecipientEmail, $sMailSubject, $sMailBody, $iRecipientID = 0, $aPlus = array(), $iEmailType = BX_EMAIL_NOTIFY, $sEmailFlag = 'html', $isDisableAlert = false, $aCustomHeaders = array(), $bAddToQueue = false)
+{
+    // make sure that recipient's email is valid and message isn't empty
+    if (!$sMailBody || !$sRecipientEmail || preg_match('/\(2\)$/', $sRecipientEmail))
+        return false;
+
+    // get recipient account
+    $oAccount = !$isDisableAlert ? BxDolAccount::getInstance($sRecipientEmail) : null;
+    $aAccountInfo = $oAccount ? $oAccount->getInfo() : false;
+
+    // don't send bulk emails if user didn't subscribed to site news or email is unconfirmed
+    if ($aAccountInfo && BX_EMAIL_MASS == $iEmailType && (!$aAccountInfo['email_confirmed'] || !$aAccountInfo['receive_news']))
+        return false;
+
+    // don't send email notifications if user didn't subscribed to notifications or email is unconfirmed
+    if ($aAccountInfo && BX_EMAIL_NOTIFY == $iEmailType && (!$aAccountInfo['email_confirmed'] || !$aAccountInfo['receive_updates']))
+        return false;
+
+    if($bAddToQueue && BxDolQueueEmail::getInstance()->add($sRecipientEmail, $sMailSubject, $sMailBody, $iRecipientID, $aPlus, $iEmailType, $sEmailFlag, $isDisableAlert, $aCustomHeaders))
+        return true;
+
+    // if profile id is provided - get profile's info
+    $aRecipientInfo = false;
+    if ($iRecipientID) {
+        $oProfile = BxDolProfile::getInstance($iRecipientID);
+        if ($oProfile)
+            $aRecipientInfo = $oProfile->getInfo();
+    }
+
+    // get site vars
+    $sEmailNotify = !$isDisableAlert ? getParam('site_email_notify') : $sRecipientEmail;
+    $sSiteTitle = !$isDisableAlert ? getParam('site_title') : 'UNA ' . BX_DOL_VERSION;
+
+    // add unsubscribe link
+    if (!$isDisableAlert && empty($aPlus['unsubscribe'])) {
+        $aPlus['unsubscribe'] = '';
+        if ($oAccount && (BX_EMAIL_MASS == $iEmailType || BX_EMAIL_NOTIFY == $iEmailType))
+            $aPlus['unsubscribe'] = ($sLink = $oAccount->getUnsubscribeLink($iEmailType)) ? '<a href="' . BX_DOL_URL_ROOT . $sLink . '">' . _t('_sys_et_txt_unsubscribe') . '</a>' : '';
+    }
+
+    // parse template
+    if ($aPlus || $iRecipientID || $oAccount) {
+        if(!is_array($aPlus))
+            $aPlus = array();
+        $oEmailTemplates = BxDolEmailTemplates::getInstance();
+        $sMailSubject = $oEmailTemplates->parseContent($sMailSubject, $aPlus, !$iRecipientID && $oAccount ? $oAccount->id() : 0, $iRecipientID);
+        $sMailBody = $oEmailTemplates->parseContent($sMailBody, $aPlus, !$iRecipientID && $oAccount ? $oAccount->id() : 0, $iRecipientID);
+    }
+
+    // email message headers
+    $sMailHeader = '';
+    foreach ($aCustomHeaders as $sHeaderName => $sHeaderValue)
+        $sMailHeader = "$sHeaderName: $sHeaderValue\r\n" . $sMailHeader;
+
+    if (!isset($aCustomHeaders['From']))
+        $sMailHeader = "From: =?UTF-8?B?" . base64_encode( $sSiteTitle ) . "?= <{$sEmailNotify}>";
+
+    if (!isset($aCustomHeaders['MIME-Version']))
+        $sMailHeader = "MIME-Version: 1.0\r\n" . $sMailHeader;
+
+    $sMailParameters = isset($aCustomHeaders['Sender']) ? "-f{$aCustomHeaders['Sender']}" : "-f{$sEmailNotify}";
+
+    $sMailSubject = !isset($aCustomHeaders['Subject']) ? '=?UTF-8?B?' . base64_encode( $sMailSubject ) . '?=' : $aCustomHeaders['Subject'];
+
+    // build data for alert handler
+    $bResult = null;
+    $aAlert = array(
+        'email' => $sRecipientEmail,
+        'subject' => $sMailSubject,
+        'body' => $sMailBody,
+        'header' => $sMailHeader,
+        'params' => $sMailParameters,
+        'recipient' => $aRecipientInfo,
+        'html' => 'html' == $sEmailFlag ? true : false,
+        'custom_headers' => $aCustomHeaders,
+        'override_result' => &$bResult,
+    );
+
+    // system alert
+    if (!$isDisableAlert) {
+        bx_alert('system', 'before_send_mail', (isset($aRecipientInfo['ID']) ? $aRecipientInfo['ID'] : 0), '', $aAlert);
+        if ($bResult !== null)
+            return $bResult;
+        unset($aAlert['override_result']);
+    }
+
+    // prepare HTML/Plain message
+    if($sEmailFlag == 'html')
+        $sMailHeader = "Content-type: text/html; charset=UTF-8\r\n" . $sMailHeader;
+    else {
+        $sMailHeader = "Content-type: text/plain; charset=UTF-8\r\n" . $sMailHeader;
+        $sMailBody = html2txt($sMailBody);
+    }
+
+    // send mail or put it into queue
+    $bResult = mail($sRecipientEmail, $sMailSubject, $sMailBody, $sMailHeader, $sMailParameters);
+
+    // system alert
+    if (!$isDisableAlert)
+        bx_alert('system', 'send_mail', (isset($aRecipientInfo['ID']) ? $aRecipientInfo['ID'] : 0), '', $aAlert);
+
+    return $bResult;
+}
+
+/*
+ * Getting an array with Templates' Names
+ */
+function get_templates_array($bEnabledOnly = true, $bShortInfo = true)
+{
+    $oDb = BxDolDb::getInstance();
+
+    $sWhereAddon = $bEnabledOnly ? " AND `enabled`='1'" : "";
+
+    if($bShortInfo)
+        return $oDb->getPairs("SELECT `uri`, `title` FROM `sys_modules` WHERE 1 AND `type`='" . BX_DOL_MODULE_TYPE_TEMPLATE . "'" . $sWhereAddon, "uri", "title");
+    else
+        return $oDb->getAllWithKey("SELECT * FROM `sys_modules` WHERE 1 AND `type`='" . BX_DOL_MODULE_TYPE_TEMPLATE . "'" . $sWhereAddon, "uri");
+}
+
+function extFileExists( $sFileSrc )
+{
+    return (file_exists( $sFileSrc ) && is_file( $sFileSrc )) ? true : false;
+}
+
+function getVisitorIP()
+{
+    $ip = "0.0.0.0";
+    if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+    } elseif (isset( $_SERVER['HTTP_CLIENT_IP']) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
+        $ip = explode(".",$_SERVER['HTTP_CLIENT_IP']);
+        $ip = $ip[3].".".$ip[2].".".$ip[1].".".$ip[0];
+    } elseif (!isset( $_SERVER['HTTP_X_FORWARDED_FOR']) || empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        if (!isset( $_SERVER['HTTP_CLIENT_IP']) && empty($_SERVER['HTTP_CLIENT_IP']) && isset($_SERVER['REMOTE_ADDR']))
+            $ip = $_SERVER['REMOTE_ADDR'];
+    }
+    return $ip;
+}
+
+function genFlag($sLang = '', $oTemplate = null)
+{
+    if (!$oTemplate)
+        $oTemplate = BxDolTemplate::getInstance();
+    $oTemplate->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flag-icon-css/css/|flag-icon.min.css');
+    $sFlag = BxDolLanguages::getInstance()->getLangFlag($sLang);
+    return '<span title="' . $sFlag . '" class="flag-icon flag-icon-' . $sFlag . '"></span>';
+}
+
+// print debug information ( e.g. arrays )
+function echoDbg( $what, $desc = '' )
+{
+    if ( $desc )
+        echo "<b>$desc:</b> ";
+    echo "<pre>";
+        print_r( $what );
+    echo "</pre>\n";
+}
+
+function echoDbgLog($mWhat, $sDesc = '', $sFileName = 'debug.log')
+{
+    $sCont =
+        '--- ' . date('r') . ' (' . BX_DOL_START . ") ---\n" .
+        $sDesc . "\n" .
+        print_r($mWhat, true) . "\n\n\n";
+
+    $rFile = fopen(BX_DIRECTORY_PATH_LOGS . $sFileName, 'a');
+    fwrite($rFile, $sCont);
+    fclose($rFile);
+}
+
+function dbgTiming($sStartMicrotime)
+{
+    $i1 = explode(' ', microtime ());
+    $i2 = explode(' ', $sStartMicrotime);
+    $iTime = ($i1[0]+$i1[1]) - ($i2[0]+$i2[1]);
+    return round($iTime, 3) . ' sec';
+}
+
+function echoJson($a)
+{
+	header('Content-type: text/html; charset=utf-8');
+
+	echo json_encode($a);
+}
+
+function clear_xss($val)
+{
+    // HTML Purifier plugin
+    global $oHtmlPurifier;
+    if (!isset($oHtmlPurifier) && !$GLOBALS['logged']['admin']) {
+
+        require_once( BX_DIRECTORY_PATH_PLUGINS . 'htmlpurifier/HTMLPurifier.standalone.php' );
+
+        HTMLPurifier_Bootstrap::registerAutoload();
+
+        $oConfig = HTMLPurifier_Config::createDefault();
+
+        $oConfig->set('Cache.SerializerPath', rtrim(BX_DIRECTORY_PATH_CACHE, '/'));
+        $oConfig->set('Cache.SerializerPermissions', BX_DOL_DIR_RIGHTS);
+
+        $oConfig->set('HTML.SafeObject', 'true');
+        $oConfig->set('Output.FlashCompat', 'true');
+        $oConfig->set('HTML.FlashAllowFullScreen', 'true');
+        $oConfig->set('Attr.AllowedFrameTargets', array('_blank', '_self', '_parent', '_top'));
+
+        if (getParam('sys_add_nofollow')) {
+            $sHost = parse_url(BX_DOL_URL_ROOT, PHP_URL_HOST);
+            $oConfig->set('URI.Host', $sHost);
+            $oConfig->set('HTML.Nofollow', 'true');
+        }
+
+        $oConfig->set('Filter.Custom', array (new HTMLPurifier_Filter_YouTube(), new HTMLPurifier_Filter_YoutubeIframe(), new HTMLPurifier_Filter_AddBxLinksClass()));
+
+	    $oConfig->set('HTML.DefinitionID', 'html5-definitions');
+		$oConfig->set('HTML.DefinitionRev', 1);
+		if ($def = $oConfig->maybeGetRawHTMLDefinition()) {
+		    $def->addElement('section', 'Block', 'Flow', 'Common');
+		    $def->addElement('nav',     'Block', 'Flow', 'Common');
+		    $def->addElement('article', 'Block', 'Flow', 'Common');
+		    $def->addElement('aside',   'Block', 'Flow', 'Common');
+		    $def->addElement('header',  'Block', 'Flow', 'Common');
+		    $def->addElement('footer',  'Block', 'Flow', 'Common');
+		    $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
+		        'src' => 'URI',
+		        'type' => 'Text',
+		        'width' => 'Length',
+		        'height' => 'Length',
+		        'poster' => 'URI',
+		        'preload' => 'Enum#auto,metadata,none',
+		        'controls' => 'Bool',
+		    ));
+		    $def->addElement('source', 'Block', 'Flow', 'Common', array(
+		        'src' => 'URI',
+		        'type' => 'Text',
+            ));
+            $def->addAttribute('a', 'data-profile-id', 'Number');
+		}
+
+        $oHtmlPurifier = new HTMLPurifier($oConfig);
+    }
+
+    if (!$GLOBALS['logged']['admin'])
+        $val = $oHtmlPurifier->purify($val);
+
+    bx_alert('system', 'clear_xss', 0, 0, array('oHtmlPurifier' => $oHtmlPurifier, 'return_data' => &$val));
+
+    return $val;
+}
+
+//--------------------------------------- friendly permalinks --------------------------------------//
+//------------------------------------------- main functions ---------------------------------------//
+function uriGenerate ($s, $sTable, $sField, $sEmpty = '-')
+{
+    $s = uriFilter($s, $sEmpty);
+    if(uriCheckUniq($s, $sTable, $sField))
+        return $s;
+
+    // cut off redundant part
+    if(get_mb_len($s) > 240)
+        $s = get_mb_substr($s, 0, 240);
+
+    // try to add date
+    $s .= '-' . date('Y-m-d');
+    if(uriCheckUniq($s, $sTable, $sField))
+        return $s;
+
+    // try to add number
+    for($i = 0 ; $i < 999 ; ++$i)
+        if(uriCheckUniq($s . '-' . $i, $sTable, $sField))
+            return ($s . '-' . $i);
+
+    return rand(0, 999999999);
+}
+
+function uriFilter ($s, $sEmpty = '-')
+{
+    if (BxTemplConfig::getInstance()->bAllowUnicodeInPreg)
+        $s = get_mb_replace ('/[^\pL^\pN]+/u', '-', $s); // unicode characters
+    else
+        $s = get_mb_replace ('/([^\d^\w]+)/u', '-', $s); // latin characters only
+
+    $s = get_mb_replace ('/([-^]+)/', '-', $s);
+    $s = get_mb_replace ('/([-]+)$/', '', $s); // remove trailing dash
+    if (!$s) $s = $sEmpty;
+    return $s;
+}
+
+function uriCheckUniq ($s, $sTable, $sField)
+{
+    $oDb = BxDolDb::getInstance();
+
+    $sSql = $oDb->prepare("SELECT 1 FROM `$sTable` WHERE `$sField`=? LIMIT 1", $s);
+    return !$oDb->query($sSql);
+}
+
+function get_mb_replace ($sPattern, $sReplace, $s)
+{
+    return preg_replace ($sPattern, $sReplace, $s);
+}
+
+function get_mb_len ($s)
+{
+    return (function_exists('mb_strlen')) ? mb_strlen($s) : strlen($s);
+}
+
+function get_mb_substr ($s, $iStart, $iLen)
+{
+    return (function_exists('mb_substr')) ? mb_substr ($s, $iStart, $iLen) : substr ($s, $iStart, $iLen);
+}
+
+function bx_mb_substr_replace($s, $sReplace, $iPosStart, $iLength)
+{
+    return mb_substr($s, 0, $iPosStart) . $sReplace . mb_substr($s, $iPosStart + $iLength);
+}
+
+function bx_mb_strpos ($s, $sReplacement, $iStart = 0)
+{
+    return mb_strpos($s, $sReplacement, $iStart);
+}
+
+/**
+ * Import class file, it automatically detects class path by its prefix or module array/name
+ *
+ * @param $sClassName - full class name or class postfix(withoit prefix) in the case of module class
+ * @param $mixedModule - module array or module name in the case of module class
+ */
+function bx_import($sClassName, $mixedModule = array())
+{
+    if (class_exists($sClassName, false))
+        return;
+
+    $aModule = false;
+    if ($mixedModule) {
+        if (is_array($mixedModule)) {
+            $aModule = $mixedModule;
+        } elseif (is_string($mixedModule)) {
+            $o = BxDolModule::getInstance($mixedModule);
+            $aModule = $o->_aModule;
+        } elseif (is_bool($mixedModule) && true === $mixedModule) {
+            $aModule = $GLOBALS['aModule'];
+        }
+    }
+
+    if ($aModule) {
+        if (class_exists($aModule['class_prefix'] . $sClassName, false))
+            return;
+        require_once (BX_DIRECTORY_PATH_MODULES . $aModule['path'] . 'classes/' . $aModule['class_prefix'] . $sClassName . '.php');
+        return;
+    }
+
+    if (0 === strncmp($sClassName, 'BxDol', 5)) {
+        if (0 === strncmp($sClassName, 'BxDolStudio', 11))
+            require_once(BX_DOL_DIR_STUDIO_CLASSES . $sClassName . '.php');
+        else
+            require_once(BX_DIRECTORY_PATH_CLASSES . $sClassName . '.php');
+        return;
+    }
+
+    if (0 === strncmp($sClassName, 'BxBase', 6)) {
+        if (0 === strncmp($sClassName, 'BxBaseMod', 9)) {
+            $aMatches = array();
+            if (preg_match('/BxBaseMod([A-Z][a-z]+)/', $sClassName, $aMatches)) {
+                require_once(BX_DIRECTORY_PATH_MODULES . 'base/' . strtolower($aMatches[1]) . '/classes/' . $sClassName . '.php');
+                return;
+            }
+        } if (0 === strncmp($sClassName, 'BxBaseStudio', 12)) {
+            require_once(BX_DOL_DIR_STUDIO_BASE . 'scripts/' . $sClassName . '.php');
+            return;
+        } else {
+            require_once(BX_DIRECTORY_PATH_BASE . 'scripts/' . $sClassName . '.php');
+            return;
+        }
+    }
+
+    if (0 === strncmp($sClassName, 'BxTempl', 7)) {
+        if(0 === strncmp($sClassName, 'BxTemplStudio', 13)) {
+            $sPath = BX_DIRECTORY_PATH_MODULES . BxDolStudioTemplate::getInstance()->getPath() . 'data/template/studio/scripts/' . $sClassName . '.php';
+        } else {
+            $sPath = BX_DIRECTORY_PATH_MODULES . BxDolTemplate::getInstance()->getPath() . 'data/template/system/scripts/' . $sClassName . '.php';
+        }
+
+        if (file_exists($sPath)) {
+            require_once($sPath);
+            return;
+        }
+
+        trigger_error ("bx_import fatal error: class (" . $sClassName . ") wasn't found", E_USER_ERROR);
+    }
+}
+
+/**
+ * used in spl_autoload_register() function, so no need to call bx_import for system classes
+ */
+function bx_autoload($sClassName)
+{
+    if (0 === strncmp($sClassName, 'BxDol', 5) || 0 === strncmp($sClassName, 'BxBase', 6) || 0 === strncmp($sClassName, 'BxTempl', 7))
+        bx_import($sClassName);
+}
+
+/**
+ * Gets an instance of class pathing necessary parameters if it's necessary.
+ *
+ * @param string $sClassName class name.
+ * @param array $aParams an array of parameters to be pathed to the constructor of the class.
+ * @param array $mixedModule an array with module description. Is used when the requested class is located in some module.
+ * @return unknown
+ */
+function bx_instance($sClassName, $aParams = array(), $mixedModule = array())
+{
+    if(isset($GLOBALS['bxDolClasses'][$sClassName]))
+        return $GLOBALS['bxDolClasses'][$sClassName];
+
+    if ($mixedModule) {
+        if (!is_array($mixedModule)) {
+            $o = BxDolModule::getInstance($mixedModule);
+            $mixedModule = $o->_aModule;
+        }
+        $sClassName = bx_ltrim_str($sClassName, $mixedModule['class_prefix']);
+        bx_import($sClassName, $mixedModule);
+        $sClassName = $mixedModule['class_prefix'] . $sClassName;
+    }
+
+    $oClass = new ReflectionClass($sClassName);
+
+    $GLOBALS['bxDolClasses'][$sClassName] = empty($aParams) ? $oClass->newInstance() : $oClass->newInstanceArgs($aParams);
+
+    return $GLOBALS['bxDolClasses'][$sClassName];
+}
+
+
+/**
+ * Escapes string/array ready to pass to js script with filtered symbols like ', " etc
+ *
+ * @param $mixedInput - string/array which should be filtered
+ * @param $iQuoteType - string escaping method: BX_ESCAPE_STR_AUTO(default), BX_ESCAPE_STR_APOS or BX_ESCAPE_STR_QUOTE
+ * @return converted string / array
+ */
+function bx_js_string ($mixedInput, $iQuoteType = BX_ESCAPE_STR_AUTO)
+{
+    $aUnits = array(
+        "\n" => "\\n",
+        "\r" => "",
+    );
+    if (BX_ESCAPE_STR_APOS == $iQuoteType) {
+        $aUnits["'"] = "\\'";
+        $aUnits['<script'] = "<scr' + 'ipt";
+        $aUnits['</script>'] = "</scr' + 'ipt>";
+    } elseif (BX_ESCAPE_STR_QUOTE == $iQuoteType) {
+        $aUnits['"'] = '\\"';
+        $aUnits['<script'] = '<scr" + "ipt';
+        $aUnits['</script>'] = '</scr" + "ipt>';
+    } else {
+        $aUnits['"'] = '&quot;';
+        $aUnits["'"] = '&apos;';
+        $aUnits["<script>"] = '&lt;script&gt;';
+        $aUnits["</script>"] = '&lt;/script&gt;';
+    }
+    return str_replace(array_keys($aUnits), array_values($aUnits), $mixedInput);
+}
+
+/**
+ * Return input string/array ready to pass to html attribute with filtered symbols like ', " etc
+ *
+ * @param $mixedInput - string/array which should be filtered
+ * @param $iQuoteType - string escaping BX_ESCAPE_STR_AUTO, BX_ESCAPE_STR_APOS or BX_ESCAPE_STR_QUOTE
+ * @return converted string / array
+ */
+function bx_html_attribute ($mixedInput, $iQuoteType = BX_ESCAPE_STR_AUTO)
+{
+    $aUnits = array ();
+    if (BX_ESCAPE_STR_APOS == $iQuoteType)
+        $aUnits["'"] = "\\'";
+    elseif (BX_ESCAPE_STR_QUOTE == $iQuoteType)
+        $aUnits['"'] = '&quot;';
+    else
+        $aUnits = array("\"" => "&quot;", "'" => "&apos;");
+
+    return str_replace(array_keys($aUnits), array_values($aUnits), $mixedInput);
+}
+
+/**
+ * Escapes string/array ready to pass to php script with filtered symbols like ', " etc
+ *
+ * @param mixed $mixedInput - string/array which should be filtered
+ * @return converted string / array
+ */
+function bx_php_string_apos ($mixedInput)
+{
+    return str_replace("'", "\\'", $mixedInput);
+}
+function bx_php_string_quot ($mixedInput)
+{
+    return str_replace('"', '\\"', $mixedInput);
+}
+
+/**
+ * Gets file contents by URL.
+ *
+ * @param string $sFileUrl - file URL to be read.
+ * @param array $aParams - an array of parameters to be pathed with URL.
+ * @param string $sMethod - post or get.
+ * @param array $aHeaders - custom headers.
+ * @param string $sHttpCode - HTTP code to return
+ * @param array $aBasicAuth - array with 'user' and 'password' for Basic HTTP Auth
+ * @return string the file's contents.
+ */
+function bx_file_get_contents($sFileUrl, $aParams = array(), $sMethod = 'get', $aHeaders = array(), &$sHttpCode = null, $aBasicAuth = array(), $iTimeout = 0, $aCustomCurlParams = array())
+{
+    if ('post' != $sMethod)
+    	$sFileUrl = bx_append_url_params($sFileUrl, $aParams);
+
+    $sResult = '';
+    if(function_exists('curl_init')) {
+        $rConnect = curl_init();
+
+        curl_setopt($rConnect, CURLOPT_USERAGENT, 'UNA ' . BX_DOL_VERSION);
+        curl_setopt($rConnect, CURLOPT_TIMEOUT, 10);
+        curl_setopt($rConnect, CURLOPT_URL, $sFileUrl);
+        curl_setopt($rConnect, CURLOPT_HEADER, NULL === $sHttpCode ? false : true);
+        curl_setopt($rConnect, CURLOPT_RETURNTRANSFER, 1);
+
+        if (0 !== $iTimeout) {
+            curl_setopt($rConnect, CURLOPT_CONNECTTIMEOUT, $iTimeout);
+            curl_setopt($rConnect, CURLOPT_TIMEOUT, $iTimeout);
+        }
+
+        if (!ini_get('open_basedir'))
+            curl_setopt($rConnect, CURLOPT_FOLLOWLOCATION, 1);
+
+        if ($aHeaders)
+            curl_setopt($rConnect, CURLOPT_HTTPHEADER, $aHeaders);
+
+        if ($aBasicAuth)
+            curl_setopt($rConnect, CURLOPT_USERPWD, $aBasicAuth['user'] . ':' . $aBasicAuth['password']);
+
+        if ('post' == $sMethod) {
+            curl_setopt($rConnect, CURLOPT_POST, true);
+            curl_setopt($rConnect, CURLOPT_POSTFIELDS, http_build_query($aParams));
+        }
+
+        $sAllCookies = '';
+        foreach($_COOKIE as $sKey=>$sValue){
+            $sAllCookies .= $sKey . '=' . $sValue . ';';
+        }
+        curl_setopt($rConnect, CURLOPT_COOKIE, $sAllCookies);
+
+        if ($aCustomCurlParams)
+            foreach ($aCustomCurlParams as $sName => $mixedValue)
+                curl_setopt($rConnect, constant($sName), $mixedValue);
+
+        $sResult = curl_exec($rConnect);
+
+        if (curl_errno($rConnect) == 60) { // CURLE_SSL_CACERT
+            curl_setopt($rConnect, CURLOPT_CAINFO, BX_DIRECTORY_PATH_PLUGINS . 'curl/cacert.pem');
+            $sResult = curl_exec($rConnect);
+        }
+
+        if (NULL !== $sHttpCode)
+            $sHttpCode = curl_getinfo($rConnect, CURLINFO_HTTP_CODE);
+
+        curl_close($rConnect);
+    }
+    else {
+
+        $iSaveTimeout = false;
+        if (0 !== $iTimeout) {
+            $iSaveTimeout = ini_get('default_socket_timeout');
+            ini_set('default_socket_timeout', $iTimeout);
+        }
+
+        $sResult = @file_get_contents($sFileUrl);
+
+        if (0 !== $iTimeout && false !== $iSaveTimeout) {
+            ini_set('default_socket_timeout', $iSaveTimeout);
+        }
+    }
+
+    return $sResult;
+}
+
+function bx_get_site_info($sSourceUrl, $aProcessAdditionalTags = array())
+{
+    $aResult = array();
+    $sContent = bx_file_get_contents($sSourceUrl);
+
+    if ($sContent) {
+        $sCharset = '';
+        preg_match("/<meta.+charset=([A-Za-z0-9-]+).+>/i", $sContent, $aMatch);
+        if (isset($aMatch[1]))
+            $sCharset = $aMatch[1];
+
+        $sContent = preg_replace("/<script[^>]*>(.*?)<\/script>/i", '', $sContent);
+        $sContent = preg_replace("/<style[^>]*>(.*?)<\/style>/i", '', $sContent);
+        if (preg_match("/<title[^>]*>(.*?)<\/title>/i", $sContent, $aMatch))
+            $aResult['title'] = strip_tags($aMatch[1]);
+        else
+            $aResult['title'] = parse_url($sSourceUrl, PHP_URL_HOST);
+
+        $aResult['description'] = bx_parse_html_tag($sContent, 'meta', 'name', 'description', 'content', $sCharset);
+        $aResult['keywords'] = bx_parse_html_tag($sContent, 'meta', 'name', 'keywords', 'content', $sCharset);
+
+        if ($aProcessAdditionalTags) {
+
+            foreach ($aProcessAdditionalTags as $k => $a) {
+                $aResult[$k] = bx_parse_html_tag(
+                    $sContent, 
+                    isset($a['tag']) ? $a['tag'] : 'meta', 
+                    isset($a['name_attr']) ? $a['name_attr'] : 'itemprop', 
+                    isset($a['name']) ? $a['name'] : $k, 
+                    isset($a['content_attr']) ? $a['content_attr'] : 'content', 
+                    $sCharset,
+                    isset($a['specialchars_decode']) ? $a['specialchars_decode'] : true); 
+
+                if ((isset($a['name']) && 'og:image' == $a['name']) || (isset($a['tag']) && 'link' == $a['tag'])) {
+                    $aResult[$k] = bx_get_site_info_fix_relative_url ($sSourceUrl, $aResult[$k]);
+                }
+            }
+
+        }
+    }
+
+    return $aResult;
+}
+
+/**
+ * Fix relative URL to make it absolute
+ * @param $sSourceUrl main URL
+ * @param $s URL to fix
+ * @return absolute URL or URL wothout changes
+ */ 
+function bx_get_site_info_fix_relative_url ($sSourceUrl, $s)
+{
+    if (0 === stripos($s, 'http://') || 0 === stripos($s, 'https://') || 0 === stripos($s, 'ftp://') || !$s)
+        return $s;
+
+    if ('/' == $s[0]) {
+        $a = parse_url($sSourceUrl);
+        return $a['scheme'] . '://' . $a['host'] . (empty($a['port']) || 80 == $a['port'] || 443 == $a['port'] ? '' : ':' . $a['port']) . $s;
+    }
+
+    return $sSourceUrl . ('/' === substr($sSourceUrl, -1) ? $s : '/' . $s);
+}
+
+function bx_parse_html_tag ($sContent, $sTag, $sAttrNameName, $sAttrNameValue, $sAttrContentName, $sCharset = false, $bSpecialCharsDecode = true)
+{
+    if (!preg_match("/<{$sTag}\s+{$sAttrNameName}[='\" ]+{$sAttrNameValue}['\"]\s+{$sAttrContentName}[='\" ]+([^'>\"]*)['\"][^>]*>/i", $sContent, $aMatch) || !isset($aMatch[1]))
+        preg_match("/<{$sTag}\s+{$sAttrContentName}[='\" ]+([^'>\"]*)['\"]\s+{$sAttrNameName}[='\" ]+{$sAttrNameValue}['\"][^>]*>/i", $sContent, $aMatch);
+
+    $s = isset($aMatch[1]) ? $aMatch[1] : '';
+
+    if ($s && $sCharset)
+        $s = mb_convert_encoding($s, 'UTF-8', $sCharset);
+
+    if ($bSpecialCharsDecode)
+        $s = htmlspecialchars_decode($s);
+        
+    return $s;
+}
+
+// calculation ini_get('upload_max_filesize') in bytes as example
+function return_bytes($val)
+{
+    $val = trim($val);
+    $last = strtolower($val{strlen($val)-1});
+    $val = (int)$val;
+    switch($last) {
+        // The 'G' modifier is available since PHP 5.1.0
+        case 'k':
+            $val *= 1024;
+            break;
+        case 'm':
+            $val *= 1024 * 1024;
+            break;
+        case 'g':
+            $val *= 1024 * 1024 * 1024;
+            break;
+    }
+    return $val;
+}
+
+// Generate Random Password
+function genRndPwd($iLength = 8, $bSpecialCharacters = true)
+{
+    $sPassword = '';
+    $sChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
+
+    if($bSpecialCharacters === true)
+        $sChars .= "!?=/&+,.";
+
+    for($i = 0; $i < $iLength; $i++) {
+        $x = mt_rand(0, strlen($sChars) -1);
+        $sPassword .= $sChars{$x};
+    }
+
+    return $sPassword;
+}
+
+// Generate Random Salt for Password encryption
+function genRndSalt()
+{
+    return genRndPwd(8, true);
+}
+
+/**
+ * Encrypt user password.
+ * When BX_PWD_ALGO is 'custom', then bx_custom_pwd_encrypt function with custom algorythm must be defined in inc/header.inc.php file.
+ * @param $sPwd clear password
+ * @param $sSalt salt
+ * @return password hash
+ */ 
+function encryptUserPwd($sPwd, $sSalt)
+{
+	$sAlgo = defined('BX_PWD_ALGO') ? BX_PWD_ALGO : '';
+
+    switch ($sAlgo) {
+        
+        case 'custom':
+            return bx_custom_pwd_encrypt($sPwd, $sSalt); 
+
+    	case 'crypt':
+            return crypt($sPwd, BX_PWD_ALGO_SALT);
+
+        case 'sha1_crypt_salt':
+            return sha1(crypt($sPwd, BX_PWD_ALGO_SALT) . $sSalt);
+
+        case 'sha1_md5_salt':
+        default:
+            return sha1(md5($sPwd) . $sSalt);
+    }
+}
+
+function bx_get ($sName, $sMethod = false)
+{
+    if (isset($_GET[$sName]) && ('get' == $sMethod || !$sMethod))
+        return $_GET[$sName];
+    elseif (isset($_POST[$sName]) && ('post' == $sMethod || !$sMethod))
+        return $_POST[$sName];
+    else
+        return false;
+}
+
+function bx_get_base_url_inline($aParams = array())
+{
+    $aBaseLink = parse_url(BX_DOL_URL_ROOT);
+    $sPageLink = (!empty($aBaseLink['scheme']) ? $aBaseLink['scheme'] : 'http') . '://' . $aBaseLink['host'] . $_SERVER['REQUEST_URI'];
+
+    list($sPageLink, $aPageParams) = bx_get_base_url($sPageLink);
+
+    if(!empty($_SERVER['QUERY_STRING'])) {
+        $aPageParamsAdd = array();
+        parse_str($_SERVER['QUERY_STRING'], $aPageParamsAdd);
+        if(!empty($aPageParamsAdd) && is_array($aPageParamsAdd))
+            $aPageParams = array_merge($aPageParams, $aPageParamsAdd);
+    }
+
+    if(!empty($aParams) && is_array($aParams))
+        $aPageParams = array_merge($aPageParams, $aParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_base_url_popup($aParams = array())
+{
+    list($sPageLink, $aPageParams) = bx_get_base_url($_SERVER['HTTP_REFERER']);
+
+    if(!empty($aParams) && is_array($aParams))
+        $aPageParams = array_merge($aPageParams, $aParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_base_url($sPageLink)
+{
+    $sPageLink = BxDolPermalinks::getInstance()->unpermalink($sPageLink, false);
+
+    $sPageParams = '';
+    if(strpos($sPageLink, '?') !== false)
+        list($sPageLink, $sPageParams) = explode('?', $sPageLink);
+
+    $aPageParams = array();
+    if(!empty($sPageParams))
+        parse_str($sPageParams, $aPageParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_encode_url_params ($a, $aExcludeKeys = array (), $aOnlyKeys = false)
+{
+    $s = '';
+    foreach ($a as $sKey => $sVal) {
+        if (in_array($sKey, $aExcludeKeys))
+            continue;
+        if (false !== $aOnlyKeys && !in_array($sKey, $aOnlyKeys))
+            continue;
+        if (is_array($sVal)) {
+            foreach ($sVal as $sSubVal) {
+                $s .= rawurlencode($sKey) . '[]=' . rawurlencode(is_array($sSubVal) ? 'array' : $sSubVal) . '&';
+            }
+        } else {
+            $s .= rawurlencode($sKey) . '=' . rawurlencode($sVal) . '&';
+        }
+    }
+    return $s;
+}
+
+function bx_append_url_params ($sUrl, $mixedParams)
+{
+    if (!$mixedParams)
+        return $sUrl;
+
+    $sParams = false == strpos($sUrl, '?') ? '?' : '&';
+
+    if (is_array($mixedParams)) {
+        foreach($mixedParams as $sKey => $sValue) {
+            if (!is_array($sValue)) {
+                $sParams .= $sKey . '=' . $sValue . '&';
+            }
+            else {
+                foreach($sValue as $sSubValue)
+                    $sParams .= $sKey . '[]=' . $sSubValue . '&';
+            }
+        }
+        $sParams = substr($sParams, 0, -1);
+    } else {
+        $sParams .= $mixedParams;
+    }
+    return $sUrl . $sParams;
+}
+
+function bx_rrmdir($directory)
+{
+    if (substr($directory,-1) == "/")
+        $directory = substr($directory,0,-1);
+
+    if (!file_exists($directory) || !is_dir($directory))
+        return false;
+    elseif (!is_readable($directory))
+        return false;
+
+    if (!($directoryHandle = opendir($directory)))
+        return false;
+
+    while ($contents = readdir($directoryHandle)) {
+        if ($contents != '.' && $contents != '..') {
+            $path = $directory . "/" . $contents;
+
+            if (is_dir($path))
+                bx_rrmdir($path);
+            else
+                unlink($path);
+        }
+    }
+
+    closedir($directoryHandle);
+
+    if (!rmdir($directory))
+        return false;
+
+    return true;
+}
+
+function bx_clear_folder ($sPath, $aExts = array ())
+{
+    if (substr($$sPath,-1) == "/")
+        $sPath = substr($sPath,0,-1);
+
+    if (!file_exists($sPath) || !is_dir($sPath))
+        return false;
+    elseif (!is_readable($sPath))
+        return false;
+
+    if (!($h = opendir($sPath)))
+        return false;
+
+    while ($sFile = readdir($h)) {
+        if ('.' == $sFile || '..' == $sFile)
+            continue;
+
+        $sFullPath = $sPath . '/' . $sFile;
+
+        if (is_dir($sFullPath))
+            continue;
+
+        if (!$aExts || (($sExt = pathinfo($sFullPath, PATHINFO_EXTENSION)) && in_array($sExt, $aExts)))
+            @unlink($sFullPath);
+    }
+
+    closedir($h);
+
+    return true;
+}
+
+function bx_ltrim_str ($sString, $sPrefix, $sReplace = '')
+{
+    if ($sReplace && substr($sString, 0, strlen($sReplace)) == $sReplace)
+        return $sString;
+    if (substr($sString, 0, strlen($sPrefix)) == $sPrefix)
+        return $sReplace . substr($sString, strlen($sPrefix));
+    return $sString;
+}
+
+function bx_rtrim_str ($sString, $sPrefix, $sReplace = '')
+{
+    if ($sReplace && substr($sString, -strlen($sReplace)) == $sReplace)
+        return $sString;
+    if (substr($sString, -strlen($sPrefix)) == $sPrefix)
+        return substr($sString, 0, -strlen($sPrefix)) . $sReplace;
+    return $sString;
+}
+
+/**
+ * Strip all lines with no information for example: <p></p>, <br /><br />
+ */ 
+function bx_trim_nl_duplicates($s)
+{
+    $sStrip = implode('', array_keys(get_html_translation_table(HTML_ENTITIES)));
+    return implode('', array_filter(mb_split("[\n\r]", $s), function($s) use ($sStrip) {
+        return trim(strip_tags($s, '<button><canvas><embed><hr><iframe><img><input><object><select><svg><video>'), $sStrip) !== '';
+    }));
+}
+
+/**
+ * Convert array to attributes string
+ *
+ * Example:
+ * @code
+ * $a = array('name' => 'test', 'value' => 5);
+ * $s = bx_convert_array2attrs($a);
+ * echo $s; // outputs: name="test" value="5"
+ * @endcode
+ *
+ * @param $a - array of attributes
+ * @param $sClasses - classes to merge with 'class' attribute
+ * @param $sStyles - styles to merge with 'style' attribute
+ * @return string
+ */
+function bx_convert_array2attrs ($a, $sClasses = false, $sStyles = false)
+{
+    $sRet = '';
+
+    if (!$a || !is_array($a))
+        $a = array();
+
+    if ($sClasses) {
+        $sClasses = trim($sClasses);
+        $a['class'] = $sClasses . (!empty($a['class']) ? ' ' . $a['class'] : '');
+    }
+
+    if ($sStyles) {
+        $sStyles = trim($sStyles);
+        if (';' != $sStyles[strlen($sStyles)-1])
+            $sStyles .= ';';
+        $a['style'] = $sStyles . (!empty($a['style']) ? ' ' . $a['style'] : '');
+    }
+
+    foreach ($a as $sKey => $sValue) {
+        if(is_null($sValue)) // pass NULL values
+            continue;
+
+        $sValueC = bx_html_attribute($sValue, BX_ESCAPE_STR_QUOTE);
+
+        $sRet .= " $sKey=\"$sValueC\"";
+    }
+
+    return $sRet;
+}
+
+function bx_unicode_urldecode($s)
+{
+    preg_match_all('/%u([[:alnum:]]{4})/', $s, $a);
+
+    foreach ($a[1] as $uniord) {
+        $dec = hexdec($uniord);
+        $utf = '';
+
+        if ($dec < 128) {
+            $utf = chr($dec);
+        } else if ($dec < 2048) {
+            $utf = chr(192 + (($dec - ($dec % 64)) / 64));
+            $utf .= chr(128 + ($dec % 64));
+        } else {
+            $utf = chr(224 + (($dec - ($dec % 4096)) / 4096));
+            $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
+            $utf .= chr(128 + ($dec % 64));
+        }
+
+        $s = str_replace('%u'.$uniord, $utf, $s);
+    }
+
+    return urldecode($s);
+}
+
+/**
+ * Raise an alert
+ * @param string $sUnit - system type
+ * @param string $sAction - system action
+ * @param int $iObjectId - object id
+ * @param int $iSender - sender (action's author) profile id, if it is false - then currectly logged in profile id is used
+ * @param array $aExtras - extra parameters 
+ */
+function bx_alert($sUnit, $sAction, $iObjectId, $iSender = false, $aExtras = array())
+{
+    $o = new BxDolAlerts($sUnit, $sAction, $iObjectId, $iSender, $aExtras);
+    $o->alert();
+}
+
+/**
+ * Perform serice call
+ * @param $mixed module name or module id
+ * @param $sMethod service method name in format 'method_name', corresponding class metod is serviceMethodName
+ * @param $aParams params to pass to service method
+ * @param $sClass class to search for service method, by default it is main module class
+ * @return service call result
+ */
+function bx_srv($mixed, $sMethod, $aParams = array(), $sClass = 'Module', $bIgnoreCache = false)
+{
+    return BxDolService::call($mixed, $sMethod, $aParams, $sClass, $bIgnoreCache);
+}
+
+function bx_replace_markers($mixed, $aMarkers)
+{
+    if (empty($aMarkers))
+        return $mixed;
+
+    if (is_array($mixed)) {
+        foreach ($mixed as $sKey => $sValue)
+            $mixed[$sKey] = bx_replace_markers ($sValue, $aMarkers);
+    } else {
+        foreach ($aMarkers as $sKey => $sValue)
+            $mixed = str_replace('{' . $sKey . '}', $sValue, $mixed);
+    }
+
+    return $mixed;
+}
+
+function bx_site_hash($sSalt = '', $isSkipVersion = false)
+{
+    return md5($sSalt . ($isSkipVersion ? '' : bx_get_ver() . getParam('sys_revision')) . BX_DOL_SECRET . BX_DOL_URL_ROOT);
+}
+
+/**
+ * Transform string to method name string, for example it changes 'some_method' string to 'SomeMethod' string
+ * @param $s string where words are separated with underscore
+ * @param $aWordsDelimiter word delimeters
+ * @return string where every word begins with capital letter
+ */
+function bx_gen_method_name ($s, $aWordsDelimiter = array('_'))
+{
+    return str_replace(' ', '', ucwords(str_replace($aWordsDelimiter, ' ', $s)));
+}
+
+/**
+ * Trigger user error
+ * @param $sMsg message to display
+ * @param $iNumLevelsBack add additional debug backtracing N levels back
+ */
+function bx_trigger_error ($sMsg, $iNumLevelsBack = 0)
+{
+    $a = debug_backtrace();
+    $sMsgAdd = "<br />\n related code in <b>{$a[$iNumLevelsBack]['file']}</b> on line <b>{$a[$iNumLevelsBack]['line']}</b> <br />\n";
+    trigger_error ($sMsg . $sMsgAdd, E_USER_ERROR);
+}
+
+/**
+ * Get system DB version, for files version @see BX_DOL_VERSION, these versions must match
+ */
+function bx_get_ver ($bInvalidateCache = false)
+{
+    $oDb = BxDolDb::getInstance();
+
+    if ($bInvalidateCache)
+        $oDb->cleanMemory('sys_version');
+    
+    $sQuery = $oDb->prepare("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+    return $oDb->fromMemory('sys_version', 'getOne', $sQuery);
+}
+
+/**
+ * Check if site maintetance mode is enabled.
+ * Maintetance mode is enabled when '.bx_maintenance' file exists in the script root folder, 
+ * please note that this is hidden file and some file managers don't show it.
+ * @param $bShowHttpError show 503 HTTP error if site is in mainenance mode
+ * @return true if site is in maintenance mode, or false otherwise
+ */
+function bx_check_maintenance_mode ($bShowHttpError = false)
+{
+    $bMaintetance = file_exists(BX_DIRECTORY_PATH_ROOT . BX_MAINTENANCE_FILE) && !defined('BX_DOL_UPGRADING');
+
+    if ($bMaintetance && $bShowHttpError)
+        bx_show_service_unavailable_error_and_exit ('Site is temporarily unavailable due to scheduled maintenance, please try again in a minute.', 600);
+
+    return $bMaintetance;
+}
+
+/**
+ * Check for minimal requirements.
+ * if BX_DISABLE_REQUIREMENTS_CHECK is defined then this requirements checking is skipped.
+ * @param $bShowHttpError show 503 HTTP error if site doesn't meet minimal requirements
+ * @return false if requirements are met, or array of errors of requirements aren't met
+ */
+function bx_check_minimal_requirements ($bShowHttpError = false)
+{
+    if (defined('BX_DISABLE_REQUIREMENTS_CHECK'))
+        return false;
+
+    $aErrors = array();
+
+    $aErrors[] = (ini_get('register_globals') == 0) ? '' : '<b>register_globals</b> is on (you need to disable it, or your site will be unsafe)';
+    $aErrors[] = (ini_get('safe_mode') == 0) ? '' : '<b>safe_mode</b> is on (you need to disable it)';
+    $aErrors[] = (version_compare(PHP_VERSION, '5.3.0', '<')) ? 'PHP version is too old (please update to <b>PHP 5.3.0</b> at least)' : '';
+    $aErrors[] = (!extension_loaded( 'mbstring')) ? '<b>mbstring</b> extension not installed (the script cannot work without it)' : '';
+    $aErrors[] = (ini_get('allow_url_include') == 0) ? '' : '<b>allow_url_include</b> is on (you need to disable it, or your site will be unsafe)';
+
+    $aErrors = array_diff($aErrors, array('')); // delete empty
+
+    $bFailedMinimalRequirements = !empty($aErrors);
+
+    if ($bFailedMinimalRequirements && $bShowHttpError) {
+        $sErrors = implode(" <br /> ", $aErrors);
+        bx_show_service_unavailable_error_and_exit($sErrors);
+    }
+
+    return $bFailedMinimalRequirements ? $aErrors : false;
+}
+
+/**
+ * Check if redirect to the correct hostname is required, for example redirect from site.com to www.site.com
+ * @param $bProcessRedirect process redirect and exit if needed
+ */
+function bx_check_redirect_to_correct_hostname ($bProcessRedirect = false)
+{
+    $aUrl = parse_url(BX_DOL_URL_ROOT);
+    $iPortDefault = 'https' == $aUrl['scheme'] ? '443' : '80';
+
+    $bRedirectRequired = isset($_SERVER['HTTP_HOST']) && 0 !== strcasecmp($_SERVER['HTTP_HOST'], $aUrl['host']) && 0 !== strcasecmp($_SERVER['HTTP_HOST'], $aUrl['host'] . ':' . (!empty($aUrl['port']) ? $aUrl['port'] : $iPortDefault));
+
+    if ($bRedirectRequired && $bProcessRedirect) {
+        $sPort = empty($aUrl['port']) || 80 == $aUrl['port'] || 443 == $aUrl['port'] ? '' : ':' . $aUrl['port'];
+        header("Location:{$aUrl['scheme']}://{$aUrl['host']}{$sPort}{$_SERVER['REQUEST_URI']}", true, 301);
+        exit;
+    }
+    
+    return $bRedirectRequired;
+}
+
+/**
+ * Check if redirect to remove install folder.
+ * If BX_SKIP_INSTALL_CHECK is defined then this redirect checking is skipped.
+ * @param $bProcessRedirect process redirect and exit if needed
+ */
+function bx_check_redirect_to_remove_install_folder ($bProcessRedirect = false)
+{
+    $bRemoveInstallFolder = !defined ('BX_SKIP_INSTALL_CHECK') && file_exists(BX_DIRECTORY_PATH_ROOT . 'install');
+
+    if ($bRemoveInstallFolder && $bProcessRedirect) {
+        header('Location:' . BX_DOL_URL_ROOT . 'install/index.php?action=remove_install');
+        exit;
+    }
+
+    return $bRemoveInstallFolder;
+}
+
+/**
+ * Show HTTP 503 service unavailable error and exit
+ */
+function bx_show_service_unavailable_error_and_exit ($sMsg = false, $iRetryAfter = 86400)
+{
+    header('HTTP/1.0 503 Service Unavailable', true, 503);
+    header('Retry-After: 600');
+    echo $sMsg ? $sMsg : 'Service temporarily unavailable';
+    exit;
+}
+
+/**
+ * The function is sumilar to php readfile, but it send all required headers and can send file by chunks and suports file seek
+ * @param $sPath path to file to output to the browser
+ * @param $sFilename filename without path, ig file is saved from browser, then this name is used, not used(empty) by default
+ * @param $sMimeType file mime type, by default 'application/octet-stream'
+ * @param $iCacheAge file cache age, by default 0
+ * @param $sCachePrivacy cache privacy 'public' (default value) or 'private'
+ * @return true on success or false on error
+ */
+function bx_smart_readfile($sPath, $sFilename = '', $sMimeType = 'application/octet-stream', $iCacheAge = 0, $sCachePrivacy = 'public')
+{
+    if (!file_exists($sPath))
+        return  false;
+
+    $fp = @fopen($sPath, 'rb');
+
+    $size   = filesize($sPath);
+    $length = $size;
+    $start  = 0;
+    $end    = $size - 1;
+
+    header('Content-type: ' . $sMimeType);
+    header('Cache-Control: ' . $sCachePrivacy . ', must-revalidate, max-age=' . $iCacheAge);
+    header("Accept-Ranges: 0-$length");
+    if ($sFilename)
+        header('Content-Disposition: inline; filename="' . $sFilename . '"');
+
+    if (isset($_SERVER['HTTP_RANGE'])) {
+
+        $c_start = $start;
+        $c_end   = $end;
+
+        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+        if (strpos($range, ',') !== false) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        if ($range == '-') {
+            $c_start = $size - substr($range, 1);
+        }else{
+            $range  = explode('-', $range);
+            $c_start = $range[0];
+            $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
+        }
+        $c_end = ($c_end > $end) ? $end : $c_end;
+        if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        $start  = $c_start;
+        $end    = $c_end;
+        $length = $end - $start + 1;
+        fseek($fp, $start);
+        header('HTTP/1.1 206 Partial Content');
+    }
+    header("Content-Range: bytes $start-$end/$size");
+    header("Content-Length: ".$length);
+
+
+    $buffer = 1024 * 8;
+    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
+
+        if ($p + $buffer > $end) {
+            $buffer = $end - $p + 1;
+        }
+        set_time_limit(0);
+        echo fread($fp, $buffer);
+        flush();
+    }
+
+    fclose($fp);
+
+    return true;
+}
+
+/**
+ * Wrap in A tag links in TEXT string
+ * @param $text - text string without tags
+ * @param $sAttrs - attributes string to add to the added A tag
+ * @param $bHtmlSpecialChars - apply htmlspecialchars before processing
+ * @return string where all links are wrapped in A tag
+ */
+function bx_linkify($text, $sAttrs = '', $bHtmlSpecialChars = false)
+{
+    if ($bHtmlSpecialChars)
+        $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+    $re = "@\b((https?://)|(www\.))(([0-9a-zA-Z_!~*'().&=+$%-]+:)?[0-9a-zA-Z_!~*'().&=+$%-]+\@)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*'().;?:\@&=+$,%#-]+)*/?)@";
+    preg_match_all($re, $text, $matches, PREG_OFFSET_CAPTURE);
+
+    $matches = $matches[0];
+
+    if ($i = count($matches))
+        $bAddNofollow = getParam('sys_add_nofollow') == 'on';
+
+    while ($i--)
+    {
+        $url = $matches[$i][0];
+        if (!preg_match('@^https?://@', $url))
+            $url = 'http://'.$url;
+
+        if (strncmp(BX_DOL_URL_ROOT, $url, strlen(BX_DOL_URL_ROOT)) !== 0) {
+            $sAttrs .= ' target="_blank" ';
+            if ($bAddNofollow)
+                $sAttrs .= ' rel="nofollow" ';
+        }
+
+        $text = substr_replace($text, '<a ' . $sAttrs . ' href="'.$url.'">'.$matches[$i][0].'</a>', $matches[$i][1], strlen($matches[$i][0]));
+    }
+	
+	// email pattern
+	$mail_pattern = "/([A-z0-9\._-]+\@[A-z0-9_-]+\.)([A-z0-9\_\-\.]{1,}[A-z])/";
+	$text = preg_replace($mail_pattern, '<a href="mailto:$1$2">$1$2</a>', $text);
+	
+    return $text;
+}
+
+/**
+ * Wrap in A tag links in HTML string, which aren't wrapped in A tag yet
+ * @param $sHtmlOrig - HTML string
+ * @param $sAttrs - attributes string to add to the added A tag
+ * @return modified HTML string, in case of errror original string is returned
+ */
+function bx_linkify_html($sHtmlOrig, $sAttrs = '') 
+{
+    if (!trim($sHtmlOrig))
+        return $sHtmlOrig;
+
+    $sId = 'bx-linkify-' . md5(microtime());
+    $dom = new DOMDocument();
+    @$dom->loadHTML('<?xml encoding="UTF-8"><div id="' . $sId . '">' . $sHtmlOrig . '</div>');
+    $xpath = new DOMXpath($dom);
+
+    foreach ($xpath->query('//text()') as $text) {
+        $frag = $dom->createDocumentFragment();
+        @$frag->appendXML(bx_linkify($text->nodeValue, $sAttrs, true));
+        $text->parentNode->replaceChild($frag, $text);
+    }
+
+    if (version_compare(PHP_VERSION, '5.3.6') >= 0)
+        $s = $dom->saveHTML($dom->getElementById($sId));
+    else
+        $s = $dom->saveXML($dom->getElementById($sId), LIBXML_NOEMPTYTAG);
+
+    if (false === $s) // in case of error return original string
+        return $sHtmlOrig;
+
+    if (false !== ($iPos = mb_strpos($s, '<html><body>')) && $iPos < mb_strpos($s, $sId))
+        $s = mb_substr($s, $iPos + 12, -15); // strip <html><body> tags and everything before them
+
+    return mb_substr($s, 54, -6); // strip added tags
+}
+
+/**
+ * Returns current site protocol http:// or https://
+ */
+function bx_proto()
+{
+    return 0 === strncmp('https', BX_DOL_URL_ROOT, 5) ? 'https' : 'http';
+}
+
+/**
+ * Checks protocol in the link
+ */
+function bx_has_proto($sLink)
+{
+    return preg_match('@^https?://@', $sLink);    
+}
+
+function bx_is_empty_array ($a)
+{
+    if (!is_array($a))
+        return true;
+    if (empty($a))
+        return true;
+    foreach ($a as $k => $v)
+        if ($v)
+            return false;
+    return true;
+}
+
+function bx_is_full_array ($a)
+{
+    if (!is_array($a))
+        return false;
+    if (empty($a))
+        return false;
+    foreach ($a as $k => $v)
+        if (empty($v))
+            return false;
+    return true;
+}
+
+function bx_is_url_in_content ($sContent)
+{
+    $a = array(
+        'http://',
+        'https://',
+        'https/',
+        'http/',
+        'www.',
+        '.com'
+    );
+    foreach ($a as $s)
+        if (false !== strpos($sContent, $s))
+            return true;
+
+    return false;
+}
+
+function bx_is_dynamic_request ()
+{
+    return bx_get('dynamic') || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'XMLHttpRequest' == $_SERVER['HTTP_X_REQUESTED_WITH']);
+}
+
+/** @} */

+ 12 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/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', '9.0.0-RC13'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 111 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/files/classes/BxBaseModFilesFormUpload.php

@@ -0,0 +1,111 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseFile Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Files upload form
+ */
+class BxBaseModFilesFormUpload extends BxBaseModTextFormEntry
+{
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+
+        $this->_sGhostTemplate = 'form_ghost_template_upload.html';
+
+        if (isset($this->aInputs['profile_id']))
+            $this->aInputs['profile_id']['value'] = bx_get('profile_id');
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if (isset($this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']]) && $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) {
+
+            $oProfile = bx_get('profile_id') ? BxDolProfile::getInstance(bx_get('profile_id')) : false;
+            $oPrivacy->setCustomPrivacy(bx_get('profile_id') && $oProfile && BxDolService::call($oProfile->getModule(), 'is_group_profile') ? true : false);
+
+            $aSave = array('db' => array('pass' => 'Xss'));
+            array_walk($this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']], function ($a, $k, $aSave) {
+                if (in_array($k, array('info', 'caption', 'value')))
+                    $aSave[0][$k] = $a;
+            }, array(&$aSave));
+            
+            $aGroupChooser = $oPrivacy->getGroupChooserCustom($CNF['OBJECT_PRIVACY_VIEW'], $oPrivacy->isCustomPrivacy());
+            
+            $this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']] = array_merge($this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']], $aGroupChooser, $aSave);
+        }
+    }
+
+    public function insert ($aValsToAdd = array(), $isIgnore = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return $this->processFiles ($CNF['FIELD_PHOTO'], 0, true);
+    }
+
+    public function processFiles ($sFieldFile, $iContentId = 0, $isAssociateWithContent = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!isset($this->aInputs[$sFieldFile]))
+            return true;
+
+        $mixedFileIds = $this->getCleanValue($sFieldFile);
+        if(!$mixedFileIds)
+            return true;
+
+        $oStorage = BxDolStorage::getObjectInstance($this->aInputs[$sFieldFile]['storage_object']);
+        if (!$oStorage)
+            return false;
+
+        $iProfileId = $this->getContentOwnerProfileId($iContentId);
+
+        $aGhostFiles = $oStorage->getGhosts ($iProfileId, $isAssociateWithContent ? 0 : $iContentId, true);
+        if (!$aGhostFiles)
+            return true;
+
+        bx_import('BxDolPrivacy');
+
+        // get values form main form to pass it to each file later
+        $aFormValues = array();
+        foreach ($this->aInputs as $aInput) {
+            if ($aInput['name'] && isset($aInput['value']) && $aInput['value'] && !is_array($aInput['value']) && $aInput['name'] != 'do_submit' && $aInput['name'] != $CNF['FIELD_PHOTO'] && $aInput['name'] != 'profile_id')
+                $aFormValues[$aInput['name']] = $aInput['value'];
+        }
+
+        $aContentIds = array();
+        foreach ($aGhostFiles as $aFile) {
+            if (CHECK_ACTION_RESULT_ALLOWED !== $this->_oModule->checkAllowedAdd())
+                continue;
+            if (is_array($mixedFileIds) && !in_array($aFile['id'], $mixedFileIds))
+                continue;
+            $iContentId = 0;
+            if ($isAssociateWithContent)
+                $iContentId = BxBaseModGeneralFormEntry::insert (array_merge(array(
+                    $CNF['FIELD_FOR_STORING_FILE_ID'] => $aFile['id'],
+                    $CNF['FIELD_TITLE'] => $this->getCleanValue('title-' . $aFile['id']),
+                    $CNF['FIELD_AUTHOR'] => bx_get('profile_id') && $this->_oModule->serviceIsAllowedAddContentToProfile(bx_get('profile_id')) ? bx_get('profile_id') : '',
+                ), $aFormValues));
+            if (!$iContentId)
+                continue;
+            $aContentIds[] = $iContentId;
+            
+            if ($aFile['private'] || (isset($aFormValues[$CNF['FIELD_ALLOW_VIEW_TO']]) && BX_DOL_PG_ALL !== $aFormValues[$CNF['FIELD_ALLOW_VIEW_TO']]))
+                $oStorage->setFilePrivate ($aFile['id'], true);
+            if (!$iContentId)
+                continue;
+
+            $this->_associalFileWithContent($oStorage, $aFile['id'], $iProfileId, $iContentId, $sFieldFile);
+            $this->_oModule->checkAllowedAdd(true);
+        }
+
+        return $aContentIds;
+    }
+}
+
+/** @} */

+ 74 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/files/classes/BxBaseModFilesFormsEntryHelper.php

@@ -0,0 +1,74 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseFile Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxBaseModFilesFormsEntryHelper extends BxBaseModTextFormsEntryHelper
+{
+    protected $_sDisplayForFormAdd;
+    protected $_sObjectNameForFormAdd;
+    
+    public function __construct($oModule)
+    {
+        parent::__construct($oModule);
+    }
+    
+    public function getObjectFormAdd ($sDisplay = false)
+    {
+        if (false === $sDisplay)
+            $sDisplay = $this->_sDisplayForFormAdd;
+
+        return BxDolForm::getObjectInstance($this->_sObjectNameForFormAdd, $sDisplay, $this->_oModule->_oTemplate);
+    }
+    
+    protected function addDataFormAction ($sDisplay = false, $sCheckFunction = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedAdd()))
+            return $this->prepareResponse(MsgBox($sMsg), $this->_bAjaxMode, 'msg');
+
+        // check and display form
+        $oForm = $this->getObjectFormAdd();
+        if (!$oForm)
+            return $this->prepareResponse(MsgBox(_t('_sys_txt_error_occured')), $this->_bAjaxMode, 'msg');
+
+        $oForm->initChecker();
+        if (!$oForm->isSubmittedAndValid())
+            return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $this->_bAjaxMode && $oForm->isSubmitted(), 'form', array(
+            	'form_id' => $oForm->getId()
+            ));
+
+        // insert data into database
+        $aValsToAdd = array ();
+        $aContentIds = $oForm->insert ($aValsToAdd);
+        if (false === $aContentIds || !is_array($aContentIds)) {
+            if (!$oForm->isValid() || !is_array($aContentIds))
+                return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $this->_bAjaxMode, 'form', array(
+                	'form_id' => $oForm->getId()
+                ));
+            else
+                return $this->prepareResponse(MsgBox(_t('_sys_txt_error_entry_creation')), $this->_bAjaxMode, 'msg');
+        }
+
+        foreach ($aContentIds as $iContentId) {
+            $sResult = $this->onDataAddAfter (getLoggedId(), $iContentId);
+            if ($sResult)
+                return $sResult;
+        }
+        
+        return array('need_redirect_after_action' => true, 'content_ids_array' => $aContentIds);
+    }
+}
+
+/** @} */

+ 508 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php

@@ -0,0 +1,508 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
+{
+    protected $_oModule;
+    protected $_bAjaxMode;
+    protected $_bDynamicMode;
+
+    public function __construct($oModule)
+    {
+        parent::__construct();
+        $this->_oModule = $oModule;
+
+        $this->_bDynamicMode = false;
+
+        $this->_bAjaxMode = false;
+        $mixedAjaxMode = bx_get('ajax_mode');
+        if($mixedAjaxMode !== false)
+        	$this->setAjaxMode($mixedAjaxMode);
+    }
+
+	public function setAjaxMode($bAjaxMode)
+    {
+        $this->_bAjaxMode = (bool)$bAjaxMode;
+        if($this->_bAjaxMode)
+        	$this->setDynamicMode(true);
+    }
+
+    public function setDynamicMode($bDynamicMode)
+    {
+        $this->_bDynamicMode = (bool)$bDynamicMode;
+    }
+
+    public function getObjectStorage()
+    {
+        return BxDolStorage::getObjectInstance($this->_oModule->_oConfig->CNF['OBJECT_STORAGE']);
+    }
+
+    public function getObjectFormAdd ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'];
+        
+        return BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+    }
+
+    public function getObjectFormEdit ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'];
+
+        return BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+    }
+
+	public function getObjectFormView ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW'];
+
+        return BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+    }
+
+    public function getObjectFormDelete ($sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_DELETE'];
+
+        return BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+    }
+
+    public function viewDataEntry ($iContentId)
+    {
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedView($aContentInfo)))
+            return MsgBox($sMsg);
+
+        return $this->_oModule->_oTemplate->entryText($aContentInfo);
+    }
+
+    public function addData ($iProfile, $aValues, $sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // check and display form
+        $oForm = $this->getObjectFormAdd($sDisplay);
+        if (!$oForm)
+            return array('code' => 1, 'message' => '_sys_txt_error_occured');
+
+        $oForm->aFormAttrs['method'] = BX_DOL_FORM_METHOD_SPECIFIC;
+        $oForm->aParams['csrf']['disable'] = true;
+        if(!empty($oForm->aParams['db']['submit_name'])) {            
+            $sSubmitName = false;
+            if (is_array($oForm->aParams['db']['submit_name'])) {
+                foreach ($oForm->aParams['db']['submit_name'] as $sVal) {
+                    if (isset($oForm->aInputs[$sVal])) {
+                        $sSubmitName = $sVal;
+                        break;
+                    }
+                }
+            } 
+            else {
+                $sSubmitName = $oForm->aParams['db']['submit_name'];
+            }
+            if ($sSubmitName && isset($oForm->aInputs[$sSubmitName]))
+                $aValues[$sSubmitName] = $oForm->aInputs[$sSubmitName]['value'];
+        }
+
+        $oForm->initChecker(array(), $aValues);
+        if (!$oForm->isSubmittedAndValid())
+            return array('code' => 2, 'message' => '_sys_txt_error_occured');
+
+        // insert data into database
+        $aValsToAdd = array ();
+        if(isset($CNF['FIELD_AUTHOR']))
+            $aValsToAdd[$CNF['FIELD_AUTHOR']] = $iProfile;
+
+        $iContentId = $oForm->insert($aValsToAdd);
+        if (!$iContentId) {
+            if (!$oForm->isValid())
+                return array('code' => 2, 'message' => '_sys_txt_error_occured');
+            else
+                return array('code' => 3, 'message' => '_sys_txt_error_entry_creation');
+        }
+
+        $sResult = $this->onDataAddAfter(BxDolProfile::getInstance($iProfile)->getAccountId(), $iContentId);
+        if($sResult)
+            return array('code' => 4, 'message' => $sResult);
+
+        // process uploaded files
+        if (isset($CNF['FIELD_PHOTO']))
+            $oForm->processFiles ($CNF['FIELD_PHOTO'], $iContentId, true);
+        
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        return array('code' => 0, 'message' => '', 'content' => $aContentInfo);
+    }
+
+    public function addDataForm ($sDisplay = false, $sCheckFunction = false)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedAdd';
+        
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction())) {
+            $oProfile = BxDolProfile::getInstance();
+            if ($oProfile && ($aProfileInfo = $oProfile->getInfo()) && $aProfileInfo['type'] == 'system' && is_subclass_of($this->_oModule, 'BxBaseModProfileModule') && $this->_oModule->serviceActAsProfile()) // special check for system profile is needed, because of incorrect error message
+                return $this->prepareResponse(MsgBox(_t('_sys_txt_access_denied')), $this->_bAjaxMode, 'msg');
+            else
+                return $this->prepareResponse(MsgBox($sMsg), $this->_bAjaxMode, 'msg');
+        }
+
+        // check and display form
+        $oForm = $this->getObjectFormAdd($sDisplay);
+        if (!$oForm)
+            return $this->prepareResponse(MsgBox(_t('_sys_txt_error_occured')), $this->_bAjaxMode, 'msg');
+
+        $oForm->initChecker();
+        if (!$oForm->isSubmittedAndValid())
+            return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $this->_bAjaxMode && $oForm->isSubmitted(), 'form', array(
+            	'form_id' => $oForm->getId()
+            ));
+
+        // insert data into database
+        $aValsToAdd = array ();
+        $iContentId = $oForm->insert ($aValsToAdd);
+        if (!$iContentId) {
+            if (!$oForm->isValid())
+                return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $this->_bAjaxMode, 'form', array(
+                	'form_id' => $oForm->getId()
+                ));
+            else
+                return $this->prepareResponse(MsgBox(_t('_sys_txt_error_entry_creation')), $this->_bAjaxMode, 'msg');
+        }
+
+        $sResult = $this->onDataAddAfter (getLoggedId(), $iContentId);
+        if ($sResult)
+            return $this->prepareResponse($sResult, $this->_bAjaxMode, 'msg');
+
+        // process uploaded files
+        if (isset($CNF['FIELD_PHOTO']))
+            $oForm->processFiles ($CNF['FIELD_PHOTO'], $iContentId, true);
+
+        // perform action
+        $this->_oModule->checkAllowedAdd(true);
+
+        // redirect
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        $this->redirectAfterAdd($aContentInfo);
+    }
+    
+    public function redirectAfterAdd($aContentInfo)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$sUrl = 'page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']];
+        if($this->_bAjaxMode)
+        	$this->prepareResponse($sUrl, $this->_bAjaxMode, 'redirect');
+		else
+        	$this->_redirectAndExit($sUrl);
+    }
+
+    public function editDataForm ($iContentId, $sDisplay = false, $sCheckFunction = false, $bErrorMsg = true)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedEdit';
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return $bErrorMsg ? MsgBox(_t('_sys_txt_error_entry_is_not_defined')) : '';
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction($aContentInfo)))
+            return $bErrorMsg ? MsgBox($sMsg) : '';
+
+        // check and display form
+        $oForm = $this->getObjectFormEdit($sDisplay);
+        if (!$oForm)
+            return $bErrorMsg ? MsgBox(_t('_sys_txt_error_occured')) : '';
+
+        $aSpecificValues = array();        
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($oMetatags->locationsIsEnabled())
+                $aSpecificValues = $oMetatags->locationGet($iContentId, empty($CNF['FIELD_LOCATION_PREFIX']) ? '' : $CNF['FIELD_LOCATION_PREFIX']);
+        }
+        $oForm->initChecker($aContentInfo, $aSpecificValues);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        // update data in the DB
+        $aTrackTextFieldsChanges = null;
+
+        $this->onDataEditBefore ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $aTrackTextFieldsChanges);
+
+        if (!$oForm->update ($aContentInfo[$CNF['FIELD_ID']], array(), $aTrackTextFieldsChanges)) {
+            if (!$oForm->isValid())
+                return $oForm->getCode();
+            else
+                return MsgBox(_t('_sys_txt_error_entry_update'));
+        }
+
+        $sResult = $this->onDataEditAfter ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm);
+        if ($sResult)
+            return $sResult;
+
+        // process uploaded files
+        if (isset($CNF['FIELD_PHOTO']))
+            $oForm->processFiles ($CNF['FIELD_PHOTO'], $iContentId, false);
+
+        // perform action
+        $this->_oModule->checkAllowedEdit($aContentInfo, true);
+        
+        // redirect
+        $this->redirectAfterEdit($aContentInfo);
+    }
+
+    protected function redirectAfterEdit($aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $this->_redirectAndExit('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function deleteDataForm ($iContentId, $sDisplay = false, $sCheckFunction = false)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedDelete';
+        
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction($aContentInfo)))
+            return MsgBox($sMsg);
+
+        // check and display form
+        $oForm = $this->getObjectFormDelete($sDisplay);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $oForm->initChecker($aContentInfo);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        if ($sError = $this->deleteData($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $oProfile, $oForm))
+            return MsgBox($sError);
+
+        // perform action
+        $this->_oModule->checkAllowedDelete($aContentInfo, true);
+
+        // redirect
+        $this->redirectAfterDelete($aContentInfo);
+    }
+
+    protected function redirectAfterDelete($aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $this->_redirectAndExit($CNF['URL_HOME'], true, array(
+            'account_id' => getLoggedId(),
+            'profile_id' => bx_get_logged_profile_id(),
+        ));
+    }
+
+    /**
+     * Delete data entry
+     * @param $iContentId entry id
+     * @param $oForm optional content info array
+     * @param $aContentInfo optional content info array
+     * @param $oProfile optional content author profile
+     * @return error string on error or empty string on success
+     */
+    public function deleteData ($iContentId, $aContentInfo = false, $oProfile = null, $oForm = null)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!$aContentInfo || !$oProfile)
+            list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if (!$aContentInfo)
+            return _t('_sys_txt_error_entry_is_not_defined');
+
+        if (!$oForm)
+            $oForm = $this->getObjectFormDelete();
+
+        if (!$oForm->delete ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo))
+            return _t('_sys_txt_error_entry_delete');
+
+        if ($sResult = $this->onDataDeleteAfter ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $oProfile))
+            return $sResult;
+
+        // create an alert
+        bx_alert($this->_oModule->getName(), 'deleted', $aContentInfo[$CNF['FIELD_ID']]);
+
+        return '';
+    }
+
+    public function viewDataForm ($iContentId, $sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if ($sMsg = $this->_processPermissionsCheckForViewDataForm ($aContentInfo, $oProfile))
+            return MsgBox($sMsg);
+
+        // get form
+        $oForm = $this->getObjectFormView($sDisplay);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        // process metatags
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($oMetatags->keywordsIsEnabled()) {
+                $aFields = $oMetatags->metaFields($aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW']);
+                $oForm->setMetatagsKeywordsData($iContentId, $aFields, $oMetatags);
+            }
+        }        
+
+        // display profile
+        $oForm->initChecker($aContentInfo);
+        return $oForm->getCode();
+    }
+
+    protected function _processPermissionsCheckForViewDataForm ($aContentInfo, $oProfile)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedView($aContentInfo)))
+            return $sMsg;
+
+        return '';
+    }
+
+    public function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
+    {
+        return '';
+    }
+
+    public function onDataEditBefore ($iContentId, $aContentInfo, &$aTrackTextFieldsChanges)
+    {
+    }
+
+    public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!empty($CNF['OBJECT_METATAGS'])) { // && isset($aTrackTextFieldsChanges['changed_fields'][$CNF['FIELD_TEXT']])) { // TODO: check if aTrackTextFieldsChanges works 
+            list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $oMetatags->metaAddAuto($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT']);
+            if (isset($CNF['FIELD_LOCATION_PREFIX']) && isset($oForm->aInputs[$CNF['FIELD_LOCATION_PREFIX']]) && $oMetatags->locationsIsEnabled())
+                $oMetatags->locationsAddFromForm($aContentInfo[$CNF['FIELD_ID']], empty($CNF['FIELD_LOCATION_PREFIX']) ? '' : $CNF['FIELD_LOCATION_PREFIX']);
+
+            if (!empty($CNF['FIELD_LABELS']) && ($aLabels = bx_get($CNF['FIELD_LABELS'])) && $oMetatags->keywordsIsEnabled()) {
+                foreach ($aLabels as $sLabel)
+                    $oMetatags->keywordsAddOne($aContentInfo[$CNF['FIELD_ID']], $sLabel, false);
+            }
+        }
+
+        return '';
+    }
+
+    public function onDataAddAfter ($iAccountId, $iContentId)
+    {
+        $this->_processMetas($iAccountId, $iContentId);
+
+        return '';
+    }
+
+    protected function _processMetas($iAccountId, $iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($aContentInfo)
+                $oMetatags->metaAddAuto($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']);
+            if ($oMetatags->locationsIsEnabled() && $aContentInfo)
+                $oMetatags->locationsAddFromForm($aContentInfo[$CNF['FIELD_ID']], $CNF['FIELD_LOCATION_PREFIX']);
+
+            if ($aContentInfo && !empty($CNF['FIELD_LABELS']) && ($aLabels = bx_get($CNF['FIELD_LABELS'])) && $oMetatags->keywordsIsEnabled()) {
+                foreach ($aLabels as $sLabel)
+                    $oMetatags->keywordsAddOne($aContentInfo[$CNF['FIELD_ID']], $sLabel, false);
+            }
+        }
+    }
+
+    protected function prepareCustomRedirectUrl($s, $aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aContentInfo[$CNF['FIELD_ID']], $this->_oModule->getName());
+
+        $aMarkers = array(
+            '{profile_id}',
+            '{content_id}',
+            '{module}',
+        );
+        $aReplacements = array(
+            $oProfile ? $oProfile->id() : 0,
+            $aContentInfo[$CNF['FIELD_ID']],
+            $this->_oModule->getName(),
+        );
+        $s = str_replace($aMarkers, $aReplacements, $s);
+
+        $s = BxDolPermalinks::getInstance()->permalink($s);
+
+        if (false === strpos($s, 'http://') && false === strpos($s, 'https://'))
+            $s = BX_DOL_URL_ROOT . $s;
+        
+        return $s;
+    }
+
+    protected function prepareResponse($mixedResponse, $bAsJson = false, $sKey = 'msg', $aAdditional = array())
+    {
+    	if(!$bAsJson)
+    		return $mixedResponse;
+
+		$aResponse = array(
+			$sKey => $mixedResponse
+		);
+
+		if(!empty($aAdditional) && is_array($aAdditional))
+			$aResponse = array_merge($aResponse, $aAdditional);
+
+		echoJson($aResponse);
+		exit;
+    }
+}
+
+/** @} */

+ 400 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/general/classes/BxBaseModGeneralMenuViewActions.php

@@ -0,0 +1,400 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolPrivacy');
+
+/**
+ * View entry social actions menu
+ */
+class BxBaseModGeneralMenuViewActions extends BxTemplMenuCustom
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_oMenuAction;
+    protected $_oMenuActionsMore;
+    protected $_oMenuSocialSharing;
+
+    protected $_iContentId;
+    protected $_aContentInfo;
+
+    protected $_bDynamicMode;
+    protected $_bShowAsButton;
+    protected $_bShowTitle;
+    protected $_sClassMiSa; // Separate class for Social Actions (View, Vote, Comment, Report, etc) menu items.
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        $this->setContentId(bx_process_input(bx_get('id'), BX_DATA_INT));
+
+        $this->_oMenuActions = null;
+        $this->_oMenuActionsMore = null;
+        $this->_oMenuSocialSharing = null;
+
+        $this->_bShowAsButton = true;
+        $this->_bShowTitle = true;
+        $this->_sClassMiSa = 'bx-base-general-ea-sa';
+    }
+
+    public function addMarkers($a)
+    {
+        $bResult = parent::addMarkers($a);
+        if($bResult && !empty($this->_oMenuSocialSharing))
+            $this->_oMenuSocialSharing->addMarkers($a);
+
+        return $bResult;
+    }
+
+    public function setContentId($iContentId)
+    {
+        $this->_iContentId = (int)$iContentId;
+
+        $this->_aContentInfo = $this->_oModule->_oDb->getContentInfoById($this->_iContentId);
+        if($this->_aContentInfo)
+            $this->addMarkers(array('content_id' => (int)$this->_iContentId));
+    }
+
+    /**
+     * Check if menu items is visible with extended checking linked to "allow*" method of particular module
+     * Associated "allow*" method with particular menu item is stored in module config in MENU_ITEM_TO_METHOD array.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isVisible($a)
+    {
+        return $this->_oModule->isMenuItemVisible($this->_sObject, $a, $this->_aContentInfo);
+    }
+
+    protected function _getMenuItemDefault($aItem)
+    {
+        $aItem['class_wrp'] = 'bx-base-general-entity-action' . (!empty($aItem['class_wrp']) ? ' ' . $aItem['class_wrp'] : '');
+
+        if($this->_bShowAsButton)
+            $aItem['class_link'] = 'bx-btn' . (!empty($aItem['class_link']) ? ' ' . $aItem['class_link'] : '');
+
+        if(!$this->_bShowTitle)
+            $aItem['bx_if:title']['condition'] = false;
+
+        return parent::_getMenuItemDefault ($aItem);
+    }
+
+    protected function _getMenuItemView($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_VIEWS']))
+            $sObject = $CNF['OBJECT_VIEWS'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxDolView::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_view_as_button' => $this->_bShowAsButton,
+            'show_do_view_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemComment($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_COMMENTS']))
+            $sObject = $CNF['OBJECT_COMMENTS'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxTemplCmts::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_comment_as_button' => $this->_bShowAsButton,
+            'show_do_comment_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+        return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemVote($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_VOTES']))
+            $sObject = $CNF['OBJECT_VOTES'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxDolVote::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_vote_as_button' => $this->_bShowAsButton,
+            'show_do_vote_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemScore($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_SCORES']))
+            $sObject = $CNF['OBJECT_SCORES'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxDolScore::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_vote_as_button' => $this->_bShowAsButton,
+            'show_do_vote_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemFavorite($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_FAVORITES']))
+            $sObject = $CNF['OBJECT_FAVORITES'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxDolFavorite::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_favorite_as_button' => $this->_bShowAsButton,
+            'show_do_favorite_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemFeature($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_FEATURED']))
+            $sObject = $CNF['OBJECT_FEATURED'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxDolFeature::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_feature_as_button' => $this->_bShowAsButton,
+            'show_do_feature_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemRepost($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sAction = !empty($aParams['action']) ? $aParams['action'] : '';
+        if(empty($sAction))
+            $sAction = 'added';
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        if(!empty($CNF['FIELD_ALLOW_VIEW_TO'])) {
+            $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iId);
+            if(isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && !in_array((int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']], array(BX_DOL_PG_ALL, BX_DOL_PG_MEMBERS)))
+                return '';
+        }
+
+        if(!BxDolRequest::serviceExists('bx_timeline', 'get_repost_element_block'))
+            return '';
+
+        $sResult = BxDolService::call('bx_timeline', 'get_repost_element_block', array(bx_get_logged_profile_id(), $this->_oModule->_oConfig->getName(), $sAction, $iId, array(
+            'show_do_repost_as_button' => $this->_bShowAsButton,
+            'show_do_repost_text' => $this->_bShowTitle
+        )));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemReport($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sObject = !empty($aParams['object']) ? $aParams['object'] : '';
+        if(empty($sObject) && !empty($CNF['OBJECT_REPORTS']))
+            $sObject = $CNF['OBJECT_REPORTS'];
+
+        $iId = !empty($aParams['id']) ? (int)$aParams['id'] : '';
+        if(empty($iId))
+            $iId = $this->_iContentId;
+
+        $oObject = !empty($sObject) ? BxDolReport::getObjectInstance($sObject, $iId) : false;
+        if(!$oObject || !$oObject->isEnabled())
+            return '';
+
+        $sResult = $oObject->getElementBlock(array(
+            'dynamic_mode' => $this->_bDynamicMode,
+            'show_do_report_as_button' => $this->_bShowAsButton,
+            'show_do_report_label' => $this->_bShowTitle
+        ));
+        if(empty($sResult))
+            return '';
+
+    	return array($sResult, $this->_sClassMiSa);
+    }
+
+    protected function _getMenuItemSocialSharingFacebook($aItem)
+    {
+        return $this->_getMenuItemByNameSocialSharing($aItem);
+    }
+
+    protected function _getMenuItemSocialSharingGoogleplus($aItem)
+    {
+        return $this->_getMenuItemByNameSocialSharing($aItem);
+    }
+
+    protected function _getMenuItemSocialSharingTwitter($aItem)
+    {
+        return $this->_getMenuItemByNameSocialSharing($aItem);
+    }
+
+    protected function _getMenuItemSocialSharingPinterest($aItem)
+    {
+        return $this->_getMenuItemByNameSocialSharing($aItem);
+    }
+
+    protected function _getMenuItemByNameActions($aItem, $aParams = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(empty($this->_oMenuActions)) {
+            $sObjectMenu = !empty($aParams['object_menu']) ? $aParams['object_menu'] : '';
+            if(empty($sObjectMenu) && !empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']))
+                $sObjectMenu = $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'];
+
+            if(empty($sObjectMenu))
+                return '';
+
+            $this->_oMenuActions = BxDolMenu::getObjectInstance($sObjectMenu);
+            if(!$this->_oMenuActions)
+                return '';
+
+            $this->_oMenuActions->setContentId($this->_iContentId);
+
+            $this->addMarkers($this->_oMenuActions->getMarkers());
+        }
+
+        $aItem = $this->_oMenuActions->getMenuItem($aItem['name']);
+        if(empty($aItem) || !is_array($aItem))
+            return false;
+
+        return $this->_getMenuItemDefault($aItem);
+    }
+
+    protected function _getMenuItemByNameSocialSharing($aItem, $aParams = array())
+    {
+        if(empty($this->_oMenuSocialSharing)) {
+            $this->_oMenuSocialSharing = BxDolMenu::getObjectInstance('sys_social_sharing');
+            $this->_oMenuSocialSharing->addMarkers($this->_aMarkers);
+        }
+
+        $aItem = $this->_oMenuSocialSharing->getMenuItem($aItem['name']);
+        if(empty($aItem) || !is_array($aItem))
+            return false;
+
+        return $this->_getMenuItemDefault($aItem);
+    }
+
+    protected function _getMenuItemByNameActionsMore($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(empty($this->_oMenuActionsMore)) {
+            if(empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE']))
+                return '';
+
+            $this->_oMenuActionsMore = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE']);
+            if(!$this->_oMenuActionsMore)
+                return '';
+
+            $this->_oMenuActionsMore->setContentId($this->_iContentId);
+            
+            $this->addMarkers($this->_oMenuActionsMore->getMarkers());
+        }
+
+        $aItem = $this->_oMenuActionsMore->getMenuItem($aItem['name']);
+        if(empty($aItem) || !is_array($aItem))
+            return false;
+
+        return $this->_getMenuItemDefault($aItem);
+    }
+}
+
+/** @} */

+ 292 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/general/classes/BxBaseModGeneralTemplate.php

@@ -0,0 +1,292 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Module representation.
+ */
+class BxBaseModGeneralTemplate extends BxDolModuleTemplate
+{
+    protected $MODULE;
+    
+    public $aMethodsToCallAddJsCss = array('entry', 'unit');
+
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+    }
+
+	public function getJsCode($sType, $aParams = array(), $mixedWrap = true)
+    {
+        $sMask = "{var} {object} = new {class}({params});";
+        if(is_array($mixedWrap) && !empty($mixedWrap['mask']))
+            $sMask = $mixedWrap['mask'];
+
+        $sBaseUri = $this->_oConfig->getBaseUri();
+        $sJsClass = $this->_oConfig->getJsClass($sType);
+        $sJsObject = $this->_oConfig->getJsObject($sType);
+
+        $aParams = array_merge(array(
+            'sActionUri' => $sBaseUri,
+            'sActionUrl' => BX_DOL_URL_ROOT . $sBaseUri,
+            'sObjName' => $sJsObject,
+        	'aHtmlIds' => array(),
+            'oRequestParams' => array()
+        ), $aParams);
+
+        $sContent = bx_replace_markers($sMask, array(
+            'var' => 'var',
+            'object' => $sJsObject, 
+            'class' => $sJsClass,
+            'params' => json_encode($aParams)
+        ));
+
+        return ($mixedWrap === true || (is_array($mixedWrap) && isset($mixedWrap['wrap']) && $mixedWrap['wrap'] === true)) ? $this->_wrapInTagJsCode($sContent) : $sContent;
+    }
+
+    public function getProfileLink($mixedProfile)
+    {
+    	if(!is_array($mixedProfile))
+            $mixedProfile = $this->getModule()->getProfileInfo((int)$mixedProfile);
+
+    	return $this->getLink('link', array(
+            'href' => $mixedProfile['link'],
+            'title' => bx_html_attribute(!empty($mixedProfile['title']) ? $mixedProfile['title'] : $mixedProfile['name']),
+            'content' => $mixedProfile['name']
+    	));
+    }
+
+    public function getLink($sTemplate, $aParams)
+    {
+    	return $this->parseHtmlByName($sTemplate . '.html', array(
+            'href' => $aParams['href'],
+            'title' => $aParams['title'],
+            'content' => $aParams['content']
+        ));
+    }
+
+    public function getTmplVarsText($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aVars = $aData;
+        $aVars['entry_title'] = !empty($CNF['FIELD_TITLE']) && isset($aData[$CNF['FIELD_TITLE']]) ? bx_process_output($aData[$CNF['FIELD_TITLE']]) : '';
+        $aVars['entry_text'] = !empty($CNF['FIELD_TEXT']) && isset($aData[$CNF['FIELD_TEXT']]) ? bx_process_output($aData[$CNF['FIELD_TEXT']], BX_DATA_HTML) : '';
+
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+
+            // keywords
+            if ($oMetatags->keywordsIsEnabled()) {
+                $aFields = array_merge($oMetatags->metaFields($aData, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW']), array('entry_title', 'entry_text'));
+                foreach ($aFields as $sField)
+                    $aVars[$sField] = $oMetatags->keywordsParse($aData[$CNF['FIELD_ID']], $aVars[$sField]);
+            }
+
+            // mentions
+            if ($oMetatags->mentionsIsEnabled()) {
+                $aFields = array_merge($oMetatags->metaFields($aData, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW'], true), array('entry_text'));
+                foreach ($aFields as $sField)
+                    $aVars[$sField] = $oMetatags->mentionsParse($aData[$CNF['FIELD_ID']], $aVars[$sField]);
+            }
+            
+            // location
+            $aVars['location'] = $oMetatags->locationsIsEnabled() ? $oMetatags->locationsString($aData[$CNF['FIELD_ID']]) : '';
+        }
+
+        unset($aVars['recipients']);
+
+        return $aVars;
+    }
+
+    function entryText ($aData, $sTemplateName = 'entry-text.html')
+    {
+        $aVars = $this->getTmplVarsText($aData);
+
+        if (empty($aVars['entry_text']))
+            return false;
+        
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    function entryLocation ($iContentId)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        if (empty($CNF['OBJECT_METATAGS']))
+            return '';
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);        
+
+        if (!($sLocationString = $oMetatags->locationsString($iContentId)))
+            return '';
+
+        $aVars = array (
+            'location' => $sLocationString
+        );
+        return $this->parseHtmlByName('entry-location.html', $aVars);
+    }
+
+	public function entryInfo($aData, $aValues = array())
+    {
+    	$CNF = $this->_oConfig->CNF;
+        $aValuesDefault = array();
+
+        if (isset($aData[$CNF['FIELD_ADDED']]))
+            $aValuesDefault[] = array(
+                'title' => _t('_sys_txt_field_created'),
+                'value' => bx_time_js($aData[$CNF['FIELD_ADDED']]),
+            );
+
+        if (isset($aData[$CNF['FIELD_CHANGED']]))
+            $aValuesDefault[] = array(
+                'title' => _t('_sys_txt_field_updated'),
+                'value' => bx_time_js($aData[$CNF['FIELD_CHANGED']]),
+            );
+
+        $aValues = array_merge($aValuesDefault, $aValues);
+
+    	return $this->parseHtmlByName('entry-info.html', array(
+    		'bx_repeat:info' => $aValues,
+    	));
+    }
+
+    function entryAllActions($sActions)
+    {
+        if(empty($sActions))
+            return '';
+
+        return $this->parseHtmlByName('entry-all-actions.html', array (
+            'actions' => $sActions
+        ));
+    }
+
+    function entryAttachments ($aData, $aParams = array())
+    {
+        return $this->entryAttachmentsByStorage($this->getModule()->_oConfig->CNF['OBJECT_STORAGE'], $aData, $aParams);
+    }
+
+    function entryAttachmentsByStorage ($sStorage, $aData, $aParams = array())
+    {
+        if (!($a = $this->getAttachments($sStorage, $aData, $aParams)))
+            return '';
+
+        $this->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flickity/|flickity.css');
+    	$this->addJs('flickity/flickity.pkgd.min.js');
+
+    	return $this->parseHtmlByName('attachments.html', array(
+            'bx_repeat:attachments' => $a,
+        ));
+    }
+
+    protected function getAttachmentsImagesTranscoders ()
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+        $oTranscoderPreview = isset($CNF['OBJECT_IMAGES_TRANSCODER_PICTURE']) && $CNF['OBJECT_IMAGES_TRANSCODER_PICTURE'] ? BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PICTURE']) : null;
+
+        return array($oTranscoder, $oTranscoderPreview);
+    }
+
+    protected function getAttachmentsVideoTranscoders ()
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        if (isset($CNF['OBJECT_VIDEOS_TRANSCODERS']) && $CNF['OBJECT_VIDEOS_TRANSCODERS'])
+            return array (
+                'poster' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster']),
+                'mp4' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']),
+                'webm' => BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['webm']),
+            );
+
+        return false;
+    }
+    
+    protected function getAttachments ($sStorage, $aData, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $oStorage = BxDolStorage::getObjectInstance($sStorage);
+
+        list($oTranscoder, $oTranscoderPreview) = $this->getAttachmentsImagesTranscoders();
+        $aTranscodersVideo = $this->getAttachmentsVideoTranscoders();
+
+        $aGhostFiles = $oStorage->getGhosts ($this->getModule()->serviceGetContentOwnerProfileId($aData[$CNF['FIELD_ID']]), $aData[$CNF['FIELD_ID']]);
+        if (!$aGhostFiles)
+            return false;
+
+        $sFilterField = isset($aParams['filter_field']) ? $aParams['filter_field'] : $CNF['FIELD_THUMB'];
+		if(!empty($sFilterField) && isset($aData[$sFilterField]))
+	        foreach ($aGhostFiles as $k => $a) {
+	            // don't show thumbnail in attachments
+	            if ($a['id'] == $aData[$sFilterField])
+	                unset($aGhostFiles[$k]);
+	        }
+
+        if(!$aGhostFiles)
+            return false;
+
+        $aAttachmnts = array();
+        foreach ($aGhostFiles as $k => $a) {
+
+            $isImage = $oTranscoder && (0 === strncmp('image/', $a['mime_type'], 6)) && $oTranscoder->isMimeTypeSupported($a['mime_type']); // preview for images, transcoder object for preview must be defined
+            $isVideo = $aTranscodersVideo && (0 === strncmp('video/', $a['mime_type'], 6)) && $aTranscodersVideo['poster']->isMimeTypeSupported($a['mime_type']); // preview for videos, transcoder object for video must be defined
+            $sUrlOriginal = $oStorage->getFileUrlById($a['id']);
+            $sImgPopupId = 'bx-messages-atachment-popup-' . $a['id'];
+
+            // images are displayed with preview and popup upon clicking
+            $a['bx_if:image'] = array (
+                'condition' => $isImage,
+                'content' => array (
+                    'url_original' => $isImage && $oTranscoderPreview ? $oTranscoderPreview->getFileUrl($a['id']) : $sUrlOriginal,
+                    'attr_file_name' => bx_html_attribute($a['file_name']),
+                    'popup_id' => $sImgPopupId,
+                    'url_preview' => $isImage ? $oTranscoder->getFileUrl($a['id']) : '',
+                    'popup' =>  BxTemplFunctions::getInstance()->transBox($sImgPopupId, '<img src="' . $sUrlOriginal . '" />', true, true),
+                ),
+            );
+
+            // videos are displayed inline
+            $a['bx_if:video'] = array (
+                'condition' => $isVideo,
+                'content' => array (
+                    'video' => $isVideo && $aTranscodersVideo ? BxTemplFunctions::getInstance()->videoPlayer(
+                        $aTranscodersVideo['poster']->getFileUrl($a['id']), 
+                        $aTranscodersVideo['mp4']->getFileUrl($a['id']), 
+                        $aTranscodersVideo['webm']->getFileUrl($a['id']),
+                        false, ''
+                    ) : '',
+                ),
+            );
+
+            // non-images are displayed as text links to original file
+            $a['bx_if:not_image'] = array (
+                'condition' => !$isImage && !$isVideo,
+                'content' => array (
+                    'url_original' => $sUrlOriginal,
+                    'attr_file_name' => bx_html_attribute($a['file_name']),
+                    'file_name' => bx_process_output($a['file_name']),
+                ),
+            );
+
+            $aAttachmnts[] = $a;
+        }
+
+        return $aAttachmnts;
+    }
+
+    public function addCssJs()
+    {
+        $this->addCss('main.css');
+    }
+}
+
+/** @} */

+ 683 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/groups/classes/BxBaseModGroupsModule.php

@@ -0,0 +1,683 @@
+<?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));
+    }
+
+    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 if the group 
+     */ 
+    public function serviceIsFan ($iGroupProfileId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        return $this->isFan($oGroupProfile->getContentId(), $iProfileId);
+    }
+
+    /**
+     * 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 (!($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)) {
+            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,
+            ));
+            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 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;
+
+        $aProfiles = $this->_oDb->getAdmins($oGroupProfile->id());
+        if(empty($aProfiles) || !is_array($aProfiles))
+            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;
+
+        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_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 . '_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' => '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' => '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;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedPost ($aDataEntry, $isPerformAction = false)
+    {
+        if (isLogged() && ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]) || ($aDataEntry[$this->_oConfig->CNF['FIELD_ID']] == BxDolProfile::getInstance()->getContentId() && BxDolProfile::getInstance()->getModule() == $this->getName())))
+            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 checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $iProfileId))
+            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 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);
+    }
+
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        $sResult = $this->checkAllowedView($aDataEntry);
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($this->_oConfig->CNF['OBJECT_PRIVACY_VIEW']);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $sResult && !in_array($aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']], array('c', 's')))
+            return $sResult;
+
+        return parent::_checkAllowedConnect ($aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    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;
+    }
+
+    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('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);
+    }
+}
+
+/** @} */

+ 479 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/notifications/classes/BxBaseModNotificationsDb.php

@@ -0,0 +1,479 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseNotifications Base classes for Notifications like modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Module database queries
+ */
+class BxBaseModNotificationsDb extends BxBaseModGeneralDb
+{
+    protected $_oConfig;
+
+    protected $_sTable;
+    protected $_sTableHandlers;
+    protected $_sTableSettings;
+    protected $_sTableSettings2Users;
+
+    protected $_sHandlerMask;
+    protected $_aDeliveryTypes;
+
+    public function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+
+        $this->_oConfig = $oConfig;
+
+        $this->_sTable = $this->_sPrefix . 'events';
+        $this->_sTableHandlers = $this->_sPrefix . 'handlers';
+        $this->_sTableSettings = $this->_sPrefix . 'settings';
+        $this->_sTableSettings2Users = $this->_sPrefix . 'settings2users';
+
+        $this->_sHandlerMask = "%s-%s";
+        $this->_aDeliveryTypes = array(BX_BASE_MOD_NTFS_DTYPE_SITE);
+    }
+
+    public function getAlertHandlerId()
+    {
+        $sQuery = $this->prepare("SELECT `id` FROM `sys_alerts_handlers` WHERE `name`=? LIMIT 1", $this->_oConfig->getObject('alert'));
+        return (int)$this->getOne($sQuery);
+    }
+
+    public function insertData($aData)
+    {
+        $aHandlers = array();
+    	$aHandlerDescriptor = $this->_oConfig->getHandlerDescriptor();
+
+    	//--- Update Timeline Handlers ---//
+        foreach($aData['handlers'] as $aHandler) {
+            $sContent = $sPrivacy = '';
+
+            $bInsert = $aHandler['type'] == BX_BASE_MOD_NTFS_HANDLER_TYPE_INSERT;
+            if($bInsert) {
+            	if(empty($aHandler['module_class']))
+            		$aHandler['module_class'] = 'Module';
+
+            	$sContent = serialize(array_intersect_key($aHandler, $aHandlerDescriptor));
+            	$sPrivacy = !empty($aHandler['module_event_privacy']) ? $aHandler['module_event_privacy'] : '';
+            }
+
+            $sQuery = $this->prepare("INSERT INTO
+                    `{$this->_sTableHandlers}`
+                SET
+                	`group`=?,
+                    `type`=?,
+                    `alert_unit`=?,
+                    `alert_action`=?,
+                    `content`=?,
+                    `privacy`=?", $aHandler['group'], $aHandler['type'], $aHandler['alert_unit'], $aHandler['alert_action'], $sContent, $sPrivacy);
+
+            if(!$this->query($sQuery))
+                continue;
+
+            $aHandlers[sprintf($this->_sHandlerMask, $aHandler['alert_unit'], $aHandler['alert_action'])] = (int)$this->lastId();
+        }
+
+        //--- Update Settings ---//
+        if(!empty($aData['settings']) && is_array($aData['settings']))
+            foreach($aData['settings'] as $aSetting) {
+                $sHandler = sprintf($this->_sHandlerMask, $aSetting['unit'], $aSetting['action']);
+                if(empty($aHandlers[$sHandler]))
+                    continue;
+
+                foreach($this->_aDeliveryTypes as $sDeliveryType) {
+                    $iOrder = (int)$this->getSetting(array('by' => 'delivery_max_order', 'delivery' => $sDeliveryType));
+
+                    foreach($aSetting['types'] as $sType) {
+                        $sTitle = $this->_oConfig->getHandlersActionTitle($aSetting['unit'], $aSetting['action'], $sType);
+
+                        $this->query("INSERT INTO `{$this->_sTableSettings}` SET `group`=:group, `handler_id`=:handler_id, `type`=:type, `delivery`=:delivery, `title`=:title, `order`=:order", array(
+                            'group' => $aSetting['group'],
+                            'handler_id' => (int)$aHandlers[$sHandler],
+                            'type' => $sType,
+                            'delivery' => $sDeliveryType,
+                            'title' => strcmp($sTitle, _t($sTitle)) !== 0 ? $sTitle : '',
+                            'order' => ++$iOrder
+                        ));
+                    }
+                }
+            }
+
+        //--- Update System Alerts ---//
+        $iHandlerId = $this->getAlertHandlerId();
+        foreach($aData['alerts'] as $aAlert) {
+            $sQuery = $this->prepare("INSERT INTO
+                    `sys_alerts`
+                SET
+                    `unit`=?,
+                    `action`=?,
+                    `handler_id`=?", $aAlert['unit'], $aAlert['action'], $iHandlerId);
+
+            $this->query($sQuery);
+        }
+    }
+
+    public function deleteData($aData)
+    {
+        $aHandlers = array();
+
+    	//--- Update Timeline Handlers ---//
+        foreach($aData['handlers'] as $aHandler) {
+            $sHandler = sprintf($this->_sHandlerMask, $aHandler['alert_unit'], $aHandler['alert_action']); 
+            $aBindings = array(
+                'alert_unit' => $aHandler['alert_unit'],
+                'alert_action' => $aHandler['alert_action'],
+
+                'group' => $aHandler['group'],
+                'type' => $aHandler['type']
+            );
+
+            $aHandlers[$sHandler] = $this->getOne("SELECT `id` FROM `{$this->_sTableHandlers}` WHERE (`alert_unit`=:alert_unit AND `alert_action`=:alert_action) OR (`group`=:group AND `type`=:type) LIMIT 1", $aBindings);
+
+            $this->query("DELETE FROM `{$this->_sTableHandlers}` WHERE (`alert_unit`=:alert_unit AND `alert_action`=:alert_action) OR (`group`=:group AND `type`=:type) LIMIT 1", $aBindings);
+        }
+
+        //--- Update Settings ---//
+        if(!empty($aData['settings']) && is_array($aData['settings']))
+            foreach($aData['settings'] as $aSetting) {
+                $sHandler = sprintf($this->_sHandlerMask, $aSetting['unit'], $aSetting['action']); 
+                if(empty($aHandlers[$sHandler]))
+                    continue;
+
+                foreach($aSetting['types'] as $sType)
+                    $this->query("DELETE FROM `ts`, `tsu` USING `{$this->_sTableSettings}` AS `ts` LEFT JOIN `{$this->_sTableSettings2Users}` AS `tsu` ON `ts`.`id`=`tsu`.`setting_id` WHERE `ts`.`handler_id`=:handler_id AND `ts`.`type`=:type", array(
+                        'handler_id' => (int)$aHandlers[$sHandler],
+                        'type' => $sType,
+                    ));
+            }
+
+        //--- Update System Alerts ---//
+        $iHandlerId = $this->getAlertHandlerId();
+        foreach($aData['alerts'] as $aAlert) {
+            $sQuery = $this->prepare("DELETE FROM
+                    `sys_alerts`
+                WHERE
+                    `unit`=? AND
+                    `action`=? AND
+                    `handler_id`=?
+                LIMIT 1", $aAlert['unit'], $aAlert['action'], $iHandlerId);
+
+            $this->query($sQuery);
+        }
+    }
+
+    public function deleteModuleEvents($aData)
+    {
+		//Delete system events.
+    	foreach($aData['handlers'] as $aHandler)
+            $this->deleteEvent(array('type' => $aHandler['alert_unit'], 'action' => $aHandler['alert_action']));
+    }
+
+    public function activateModuleEvents($aData, $bActivate = true)
+    {
+    	$iActivate = $bActivate ? 1 : 0;
+
+    	//Activate (deactivate) system events.
+    	foreach($aData['handlers'] as $aHandler)    		
+            $this->updateEvent(array('active' => $iActivate), array('type' => $aHandler['alert_unit'], 'action' => $aHandler['alert_action']));
+    }
+
+    public function getHandlers($aParams = array())
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sWhereClause = '';
+
+        if(!empty($aParams))
+            switch($aParams['type']) {
+                case 'by_group_key_type':
+                    $aMethod['name'] = 'getAllWithKey';
+                    $aMethod['params'][1] = 'type';
+                    $aMethod['params'][2] = array(
+                            'group' => $aParams['group']
+                    );
+
+                    $sWhereClause = "AND `group`=:group";
+                    break;
+            }
+
+        $aMethod['params'][0] = "SELECT * FROM `{$this->_sTableHandlers}` WHERE 1 " . $sWhereClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getSetting($aParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sSelectClause = "`ts`.*, `th`.`alert_unit` AS `unit`, `th`.`alert_action` AS `action`";
+    	$sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+        switch($aParams['by']) {
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `ts`.`id`=:id";
+                break;
+
+            case 'tsu_id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sJoinClause = "LEFT JOIN `" . $this->_sTableSettings2Users . "` AS `tsu` ON `ts`.`id`=`tsu`.`setting_id`";
+                $sWhereClause = " AND `tsu`.`id`=:id";
+                break;
+
+            case 'tsu_allowed':
+                $aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'handler_id' => $aParams['handler_id'], 
+                    'delivery' => $aParams['delivery'], 
+                    'type' => $aParams['type'], 
+                    'user_id' => $aParams['user_id']
+                );
+
+                $sSelectClause = "`ts`.`active` AS `active_adm`, IF(NOT ISNULL(`tsu`.`active`), `tsu`.`active`, 1) AS `active_pnl`";
+                $sJoinClause = "LEFT JOIN `" . $this->_sTableSettings2Users . "` AS `tsu` ON `ts`.`id`=`tsu`.`setting_id` AND `tsu`.`user_id`=:user_id";
+                $sWhereClause = " AND `ts`.`handler_id`=:handler_id AND `ts`.`delivery`=:delivery AND `ts`.`type`=:type";
+                break;
+
+            case 'delivery_max_order':
+                $aMethod['name'] = 'getOne';
+                $aMethod['params'][1] = array(
+                    'delivery' => $aParams['delivery'],
+                );
+
+                $sSelectClause = "`ts`.`order`";
+                $sWhereClause = " AND `ts`.`delivery`=:delivery";
+                $sOrderClause = "`ts`.`order` DESC";
+                $sLimitClause = "1";
+                break;
+
+            case 'group_type_delivery':
+                $aMethod['name'] = 'getColumn';
+                $aMethod['params'][1] = array(
+                    'group' => $aParams['group'],
+                    'delivery' => $aParams['delivery'],
+                    'type' => $aParams['type']
+                );
+
+                $sSelectClause = "`ts`.`id`";
+                $sWhereClause = " AND `ts`.`group`=:group AND `ts`.`delivery`=:delivery AND `ts`.`type`=:type";
+                if($aParams['active'])
+                     $sWhereClause .= " AND `ts`.`active`='1'";
+                break;
+
+            case 'user_id_pairs':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'active';
+                $aMethod['params'][3] = array(
+                    'user_id' => $aParams['user_id']
+                );
+
+                $sSelectClause = "`ts`.`id` AS `id`, `tsu`.`active` AS `active`";
+                $sJoinClause = "LEFT JOIN `" . $this->_sTableSettings2Users . "` AS `tsu` ON `ts`.`id`=`tsu`.`setting_id`";
+                $sWhereClause = " AND `tsu`.`user_id`=:user_id";
+                break;
+
+            case 'all_active':
+                $sWhereClause = " AND `ts`.`active`='1'";
+                break;
+
+            case 'all_inactive':
+                $sWhereClause = " AND `ts`.`active`='0'";
+                break;
+        }
+
+        $sOrderClause = !empty($sOrderClause) ? "ORDER BY " . $sOrderClause : $sOrderClause;
+        $sLimitClause = !empty($sLimitClause) ? "LIMIT " . $sLimitClause : $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `" . $this->_sTableSettings . "` AS `ts`
+            LEFT JOIN `" . $this->_sTableHandlers . "` AS `th` ON `ts`.`handler_id`=`th`.`id` " . $sJoinClause . "
+            WHERE 1" . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function updateSetting($aParamsSet, $aParamsWhere)
+    {
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        $sSql = "UPDATE `{$this->_sTableSettings}` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND ");
+        return $this->query($sSql);
+    }
+
+    public function activateSettingById($bActive, $mixedId)
+    {
+        if(!is_array($mixedId))
+            $mixedId = array($mixedId);
+
+        return $this->query("UPDATE `{$this->_sTableSettings}` SET `active`=:active WHERE `id` IN (" . $this->implode_escape($mixedId) . ")", array(
+            'active' => (int)$bActive
+        ));
+    }
+
+    public function activateSettingByIdUser($bActive, $iUserId, $mixedSettingId)
+    {
+        if(!is_array($mixedSettingId))
+            $mixedSettingId = array($mixedSettingId);
+
+        return $this->query("UPDATE `{$this->_sTableSettings2Users}` SET `active`=:active WHERE `user_id`=:user_id AND `setting_id` IN (" . $this->implode_escape($mixedSettingId) . ")", array(
+            'user_id' => $iUserId, 
+            'active' => (int)$bActive
+        ));
+    }
+
+    public function initSettingUser($iUserId)
+    {
+        $aSettingsAll = $this->getSetting(array('by' => 'all_active'));
+        $aSettingsUser = $this->getSetting(array('by' => 'user_id_pairs', 'user_id' => $iUserId));
+
+        foreach($aSettingsAll as $aSetting) {
+            if(isset($aSettingsUser[$aSetting['id']]))
+                continue;
+
+            $this->insertSettingUser(array(
+                'user_id' => $iUserId,
+                'setting_id' => $aSetting['id']
+            ));
+        }
+
+        $aSettingsAll = $this->getSetting(array('by' => 'all_inactive'));
+        foreach($aSettingsAll as $aSetting) {
+            if(!isset($aSettingsUser[$aSetting['id']]))
+                continue;
+
+            $this->deleteSettingUser(array(
+                'user_id' => $iUserId,
+                'setting_id' => $aSetting['id']
+            ));
+        }
+    }
+
+    public function insertSettingUser($aParamsSet)
+    {
+        if(empty($aParamsSet))
+            return 0;
+
+        if((int)$this->query("INSERT INTO `{$this->_sTableSettings2Users}` SET " . $this->arrayToSQL($aParamsSet)) <= 0)
+            return 0;
+
+        return (int)$this->lastId();
+    }
+
+    public function deleteSettingUser($aParamsWhere)
+    {
+        if(empty($aParamsWhere))
+            return false;
+
+        return (int)$this->query("DELETE FROM `{$this->_sTableSettings2Users}` WHERE " . $this->arrayToSQL($aParamsWhere, ' AND ')) <= 0;
+    }
+
+    public function insertEvent($aParamsSet)
+    {
+        if(empty($aParamsSet))
+            return 0;
+
+        $aSet = array();
+        foreach($aParamsSet as $sKey => $sValue)
+           $aSet[] = "`" . $sKey . "`=:" . $sKey;
+
+		if(!isset($aParamsSet['date']))
+			$aSet[] = "`date`=UNIX_TIMESTAMP()";
+
+        if((int)$this->query("INSERT INTO `{$this->_sTable}` SET " . implode(", ", $aSet), $aParamsSet) <= 0)
+            return 0;
+
+        return (int)$this->lastId();
+    }
+
+    public function updateEvent($aParamsSet, $aParamsWhere)
+    {
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        $sSql = "UPDATE `{$this->_sTable}` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND ");
+        return $this->query($sSql);
+    }
+
+    public function deleteEvent($aParams, $sWhereAddon = "")
+    {
+        $sSql = "DELETE FROM `{$this->_sTable}` WHERE " . $this->arrayToSQL($aParams, " AND ") . $sWhereAddon;
+        return $this->query($sSql);
+    }
+
+    public function getEvents($aParams, $bReturnCount = false)
+    {
+        list($sMethod, $sSelectClause, $sJoinClause, $sWhereClause, $sOrderClause, $sLimitClause) = $this->_getSqlPartsEvents($aParams);
+
+        $sSql = "SELECT {select}
+            FROM `{$this->_sTable}`
+            LEFT JOIN `{$this->_sTableHandlers}` ON `{$this->_sTable}`.`type`=`{$this->_sTableHandlers}`.`alert_unit` AND `{$this->_sTable}`.`action`=`{$this->_sTableHandlers}`.`alert_action` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " {order} {limit}";
+
+        $aEntries = $this->$sMethod(str_replace(array('{select}', '{order}', '{limit}'), array($sSelectClause, $sOrderClause, $sLimitClause), $sSql));
+        if(!$bReturnCount)
+        	return $aEntries;
+
+        return array($aEntries, (int)$this->getOne(str_replace(array('{select}', '{order}', '{limit}'), array("COUNT(*)", "", ""), $sSql)));
+    }
+
+    protected function _getSqlPartsEvents($aParams)
+    {
+    	$sMethod = 'getAll';
+        $sSelectClause = "`{$this->_sTable}`.*";
+        $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['browse']) {
+            case 'id':
+                $sMethod = 'getRow';
+                $sWhereClause = $this->prepareAsString("AND `{$this->_sTable}`.`id`=? ", $aParams['value']);
+                $sLimitClause = "LIMIT 1";
+                break;
+
+            case 'first':
+                    $sMethod = 'getRow';
+                    list($sJoinClause, $sWhereClause) = $this->_getSqlPartsEventsList($aParams);
+                    $sOrderClause = "ORDER BY `{$this->_sTable}`.`date` DESC, `{$this->_sTable}`.`id` DESC";
+                    $sLimitClause = "LIMIT 1";
+                    break;
+
+            case 'last':
+                    $sMethod = 'getRow';
+                    list($sJoinClause, $sWhereClause) = $this->_getSqlPartsEventsList($aParams);
+                    $sOrderClause = "ORDER BY `{$this->_sTable}`.`date` ASC, `{$this->_sTable}`.`id` ASC";
+                    $sLimitClause = "LIMIT 1";
+                    break;
+
+            case 'list':
+                    list($sJoinClause, $sWhereClause) = $this->_getSqlPartsEventsList($aParams);
+                    $sOrderClause = "ORDER BY `{$this->_sTable}`.`date` DESC, `{$this->_sTable}`.`id` DESC";
+                    $sLimitClause = isset($aParams['per_page']) ? "LIMIT " . $aParams['start'] . ", " . $aParams['per_page'] : "";
+                    break;
+        }
+
+        return array($sMethod, $sSelectClause, $sJoinClause, $sWhereClause, $sOrderClause, $sLimitClause);
+    }
+
+    protected function _getSqlPartsEventsList($aParams)
+    {
+    	$sJoinClause = $sWhereClause = "";
+
+    	return array($sJoinClause, $sWhereClause);
+    }
+}
+
+/** @} */

+ 205 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/notifications/classes/BxBaseModNotificationsModule.php

@@ -0,0 +1,205 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseNotifications Base classes for Notifications like modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_BASE_MOD_NTFS_HANDLER_TYPE_INSERT', 'insert');
+define('BX_BASE_MOD_NTFS_HANDLER_TYPE_UPDATE', 'update');
+define('BX_BASE_MOD_NTFS_HANDLER_TYPE_DELETE', 'delete');
+
+define('BX_BASE_MOD_NTFS_TYPE_OWNER', 'owner');
+define('BX_BASE_MOD_NTFS_TYPE_OBJECT_OWNER', 'object_owner');
+define('BX_BASE_MOD_NTFS_TYPE_CONNECTIONS', 'connections');
+define('BX_BASE_MOD_NTFS_TYPE_PUBLIC', 'public');
+
+/**
+ * DTYPE - Delivery Type:
+ * 1. by Onsite notification,
+ * 2. by Email message,
+ * 3. by Push notification.
+ */
+define('BX_BASE_MOD_NTFS_DTYPE_SITE', 'site');
+define('BX_BASE_MOD_NTFS_DTYPE_EMAIL', 'email');
+define('BX_BASE_MOD_NTFS_DTYPE_PUSH', 'push');
+
+/**
+ * Base module class.
+ */
+class BxBaseModNotificationsModule extends BxBaseModGeneralModule
+{
+	public $_iOwnerId;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_oConfig->init($this->_oDb);
+
+        $this->_iOwnerId = 0;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_notifications Base Notifications
+     * @subsection bx_base_notifications-other Other
+     * @subsubsection bx_base_notifications-add_handlers add_handlers
+     * 
+     * @code bx_srv('bx_notifications', 'add_handlers', [...]); @endcode
+     * 
+     * Register handlers for specified module.
+     *
+     * @param $sModuleUri string with module URI.
+     * 
+     * @see BxBaseModNotificationsModule::serviceAddHandlers
+     */
+    /** 
+     * @ref bx_base_notifications-add_handlers "add_handlers"
+     */
+	public function serviceAddHandlers($sModuleUri)
+    {
+        $this->_updateModuleData('add_handlers', $sModuleUri);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_notifications Base Notifications
+     * @subsection bx_base_notifications-other Other
+     * @subsubsection bx_base_notifications-delete_handlers delete_handlers
+     * 
+     * @code bx_srv('bx_notifications', 'delete_handlers', [...]); @endcode
+     * 
+     * Unregister handlers for specified module.
+     *
+     * @param $sModuleUri string with module URI.
+     * 
+     * @see BxBaseModNotificationsModule::serviceDeleteHandlers
+     */
+    /** 
+     * @ref bx_base_notifications-delete_handlers "delete_handlers"
+     */
+    public function serviceDeleteHandlers($sModuleUri)
+    {
+        $this->_updateModuleData('delete_handlers', $sModuleUri);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_notifications Base Notifications
+     * @subsection bx_base_notifications-other Other
+     * @subsubsection bx_base_notifications-delete_module_events delete_module_events
+     * 
+     * @code bx_srv('bx_notifications', 'delete_module_events', [...]); @endcode
+     * 
+     * Delete all events for specified module.
+     *
+     * @param $sModuleUri string with module URI.
+     * 
+     * @see BxBaseModNotificationsModule::serviceDeleteModuleEvents
+     */
+    /** 
+     * @ref bx_base_notifications-delete_module_events "delete_module_events"
+     */
+	public function serviceDeleteModuleEvents($sModuleUri)
+    {
+        $this->_updateModuleData('delete_module_events', $sModuleUri);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_base_notifications Base Notifications
+     * @subsection bx_base_notifications-other Other
+     * @subsubsection bx_base_notifications-get_actions_checklist get_actions_checklist
+     * 
+     * @code bx_srv('bx_notifications', 'get_actions_checklist', [...]); @endcode
+     * 
+     * Get available actions for module settings in Studio.
+     *
+     * @return an array with available actions represented as key => value pairs.
+     * 
+     * @see BxBaseModNotificationsModule::serviceGetActionsChecklist
+     */
+    /** 
+     * @ref bx_base_notifications-get_actions_checklist "get_actions_checklist"
+     */
+    function serviceGetActionsChecklist()
+    {
+    	$sLangPrefix = $this->_oConfig->getPrefix('language');
+        $aHandlers = $this->_oConfig->getHandlers();
+
+        $aResults = array();
+        foreach($aHandlers as $aHandler) {
+            if($aHandler['type'] != BX_BASE_MOD_NTFS_HANDLER_TYPE_INSERT)
+                continue;
+
+            $sUnit = _t($this->_oConfig->getHandlersUnitTitle($aHandler['alert_unit']));
+
+            $sAction = '';
+            if(!empty($aHandler['alert_action']))
+            	$sAction = ' (' . _t($this->_oConfig->getHandlersActionTitle($aHandler['alert_unit'], $aHandler['alert_action'])) . ')';
+
+            $aResults[$aHandler['id']] = $sUnit . $sAction;
+        }
+
+        asort($aResults);
+        return $aResults;
+    }
+
+    public function isAllowedView($aEvent, $bPerform = false)
+    {
+		return true;
+    }
+
+    public function getOwnerId()
+    {
+    	return $this->_iOwnerId;
+    }
+
+    /*
+     * Retrieve the Privacy from an array (Alert's Extras or Content field from DB) 
+     */
+    public function getObjectPrivacyView($aData)
+    {
+        return is_array($aData) && isset($aData['privacy_view']) ? (int)$aData['privacy_view'] : $this->_oConfig->getPrivacyViewDefault('object');
+    }
+
+    protected function _updateModuleData($sAction, $sModuleUri)
+    {
+    	$sMethod = $this->_oConfig->getHandlersMethod();
+
+        $aModule = $this->_oDb->getModuleByUri($sModuleUri);
+		if(!BxDolRequest::serviceExists($aModule, $sMethod))
+        	return;
+
+		$aData = BxDolService::call($aModule['name'], $sMethod);
+		if(empty($aData) || !is_array($aData))
+        	return;
+
+		switch($sAction) {
+			case 'add_handlers':
+				$this->_oDb->insertData($aData);
+				BxDolAlerts::cacheInvalidate();
+
+				$this->_oDb->activateModuleEvents($aData, true);
+				break;
+
+			case 'delete_handlers':
+				$this->_oDb->deleteData($aData);
+				BxDolAlerts::cacheInvalidate();
+
+				$this->_oDb->activateModuleEvents($aData, false);
+				break;
+
+			case 'delete_module_events':
+				$this->_oDb->deleteModuleEvents($aData);
+				break;
+		}
+    }
+}
+
+/** @} */

+ 77 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/notifications/classes/BxBaseModNotificationsResponse.php

@@ -0,0 +1,77 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseNotifications Base classes for Notifications like modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModNotificationsResponse extends BxDolAlertsResponse
+{
+    protected $_oModule;
+
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Note. Check priprity is IMPORTANT!
+     * Don't swap the checks without a REAL need.
+     */
+	protected function _getObjectOwnerId($aExtras)
+    {
+        if(!is_array($aExtras))
+            return 0;
+
+        if(isset($aExtras['parent_author_id']))
+    		return (int)$aExtras['parent_author_id'];
+
+        if(isset($aExtras['object_author_id']))
+    		return (int)$aExtras['object_author_id'];
+
+        return 0;
+    }
+
+    protected function _getObjectPrivacyView($aExtras)
+    {
+        return $this->_oModule->getObjectPrivacyView($aExtras);
+    }
+
+    /**
+     * Note. Check priprity is NOT IMPORTANT!
+     */
+    protected function _getSubObjectId($aExtras)
+    {
+        if(!is_array($aExtras))
+            return 0;
+
+    	if(isset($aExtras['comment_id']))
+            return (int)$aExtras['comment_id'];
+
+        if(isset($aExtras['vote_id']))
+            return (int)$aExtras['vote_id'];
+
+        if(isset($aExtras['repost_id']))
+            return (int)$aExtras['repost_id'];
+
+        if(isset($aExtras['timeline_post_id']))
+            return (int)$aExtras['timeline_post_id'];
+
+        if(isset($aExtras['performer_id']))
+            return (int)$aExtras['performer_id'];
+        
+        if(isset($aExtras['subobject_id']))
+            return (int)$aExtras['subobject_id'];
+
+        if(isset($aExtras['subobjects_ids']))
+            return is_array($aExtras['subobjects_ids']) ? $aExtras['subobjects_ids'] : array($aExtras['subobjects_ids']);
+
+        return 0;
+    }
+}
+
+/** @} */

+ 198 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfileAlertsResponse.php

@@ -0,0 +1,198 @@
+<?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 BxBaseModProfileAlertsResponse extends BxBaseModGeneralAlertsResponse
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function response($oAlert)
+    {
+        parent::response($oAlert);
+
+        $CNF = $this->_oModule->_oConfig->CNF;
+        $sModule = $this->_oModule->getName();
+
+        // update picture field id when file is deleted
+        if ($CNF['OBJECT_STORAGE'] == $oAlert->sUnit && 'file_deleted' == $oAlert->sAction && isset($CNF['FIELD_PICTURE'])) {
+            $bResult = (int)$this->_oModule->_oDb->resetContentPictureByFileId($oAlert->iObject, $CNF['FIELD_PICTURE']) > 0;
+            if($bResult)
+                bx_alert($sModule, 'profile_picture_deleted', $oAlert->iObject);
+        }
+
+        if ($CNF['OBJECT_STORAGE_COVER'] == $oAlert->sUnit && 'file_deleted' == $oAlert->sAction && isset($CNF['FIELD_COVER'])) {
+            $bResult = (int)$this->_oModule->_oDb->resetContentPictureByFileId($oAlert->iObject, $CNF['FIELD_COVER']) > 0;
+            if($bResult)
+                bx_alert($sModule, 'profile_cover_deleted', $oAlert->iObject);
+        }
+
+        // connection events
+        if ($oAlert->sUnit == 'sys_profiles_friends' && $oAlert->sAction == 'connection_added') {
+            if((int)$oAlert->aExtras['mutual'] == 0)
+                $this->sendMailFriendRequest($oAlert);
+        }
+
+        /*
+         * Re-translate timeline alert for timeline posts made by other profiles.
+         * It's used with Notifications module in the following circumstances:
+         * 1. User1 follow User2.
+         * 2. User1 doesn't follow User3.
+         * 3. User3 posts something on User2's timeline.
+         * Result: User1 would be notifiend when the timeline of following profile (User2) was update by 3d party user.
+         */
+        if($oAlert->sUnit == 'bx_timeline' && $oAlert->sAction == 'post_common' && !empty($oAlert->aExtras['object_author_id'])) {
+            $oTimelineOwner = BxDolProfile::getInstance($oAlert->aExtras['object_author_id']);
+            if($oTimelineOwner && $oTimelineOwner->getModule() == $this->_oModule->getName() && $oTimelineOwner->id() != $oAlert->iSender) {
+                $aContentInfo = $this->_oModule->serviceGetContentInfoById($oTimelineOwner->getContentId());
+
+                /*
+                 * Note. Timeline owner profile ID is used as alert sender and also a content (group) owner (author).
+                 */
+                $iSenderId = $iObjectAuthorId = $oTimelineOwner->id(); 
+                bx_alert($this->_oModule->getName(), 'timeline_post_common', $aContentInfo[$CNF['FIELD_ID']], $iSenderId, array(
+                    'object_author_id' => $iObjectAuthorId,
+                    'timeline_post_id' => $oAlert->iObject, 
+                    'timeline_post_author_id' => $oAlert->iSender,
+
+                    'content' => $aContentInfo,
+
+                    'group_profile' => $oTimelineOwner->id(), 
+                    'profile' => $oAlert->iSender,
+                ));
+            }
+        }
+
+        if ($this->MODULE != $oAlert->sUnit)
+            return;
+
+        // timeline events to override permissions
+        switch ($oAlert->sAction) {
+        case 'timeline_view':
+            $this->processTimelineView($oAlert, $oAlert->iObject);
+            break;
+
+        case 'timeline_comment':
+        case 'timeline_report':
+        case 'timeline_vote':
+            $this->processTimelineEventsBoolResult($oAlert, $oAlert->iObject);
+            break;
+
+        case 'timeline_post':
+            $this->processTimelineEventsCheckResult($oAlert, $oAlert->iObject);
+            break;
+
+        case 'timeline_delete':
+            $this->processTimelineEventsCheckResult($oAlert, $oAlert->iObject, 'checkAllowedEdit');
+            break;
+
+        case 'timeline_repost':
+            $this->processTimelineRepost($oAlert, $oAlert->iObject);
+            break;
+        }
+
+    }
+
+    protected function sendMailFriendRequest ($oAlert)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if(empty($CNF['EMAIL_FRIEND_REQUEST']) || empty($CNF['URI_VIEW_FRIEND_REQUESTS']))
+            return;
+
+        $iRecipient = $oAlert->aExtras['content'];
+        $oRecipient = BxDolProfile::getInstance($iRecipient);
+        if($oRecipient->getModule() != $this->MODULE)
+            return;
+
+        $iSender = $oAlert->aExtras['initiator'];
+        $oSender = BxDolProfile::getInstance($iSender);
+        sendMailTemplate($CNF['EMAIL_FRIEND_REQUEST'], 0, $iRecipient, array(
+            'SenderUrl' => $oSender->getUrl(),
+            'SenderDisplayName' => $oSender->getDisplayName(),
+            'FriendsLink' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_FRIEND_REQUESTS'] . '&profile_id=' . $iRecipient),
+        ), BX_EMAIL_NOTIFY);
+    }
+
+    protected function processTimelineView ($oAlert, $iGroupProfileId, $bDisableOwnerActions = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if (!$oGroupProfile) 
+            return;
+
+        $bDisableOwnerActions = !BxDolService::call($oGroupProfile->getModule(), 'act_as_profile');
+
+        $aContentInfo = $this->_oModule->serviceGetContentInfoById($oGroupProfile->getContentId());
+        if (CHECK_ACTION_RESULT_ALLOWED !== $this->_oModule->checkAllowedView($aContentInfo)) {
+            $oAlert->aExtras['override_content'] = MsgBox(_t('_sys_access_denied_to_private_content'));
+        }
+
+        if ($bDisableOwnerActions && isset($oAlert->aExtras['menu'])) {
+            foreach ($oAlert->aExtras['menu'] as $i => $r) {
+                if ('timeline-view-owner' == $r['id'] || 'timeline-view-other'  == $r['id'])
+                    unset($oAlert->aExtras['menu'][$i]);
+            }
+        }
+    }
+
+    protected function processTimelineEventsCheckResult ($oAlert, $iGroupProfileId, $sFunc = 'checkAllowedPost')
+    {
+        if ($oAlert->aExtras['check_result'][CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return;
+
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if (!$oGroupProfile) 
+            return;
+
+        $aContentInfo = $this->_oModule->serviceGetContentInfoById($oGroupProfile->getContentId());
+        if (CHECK_ACTION_RESULT_ALLOWED === ($s = $this->_oModule->$sFunc($aContentInfo))) {
+            $oAlert->aExtras['check_result'][CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+        } 
+        else {
+            $oAlert->aExtras['check_result'][CHECK_ACTION_RESULT] = CHECK_ACTION_MESSAGE_NOT_ALLOWED;
+            $oAlert->aExtras['check_result'][CHECK_ACTION_MESSAGE] = $s;
+        }
+    }
+
+    protected function processTimelineEventsBoolResult ($oAlert, $iGroupProfileId, $sFunc = 'checkAllowedPost')
+    {
+        if (isAdmin() || !$oAlert->aExtras['result'])
+            return;
+
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if (!$oGroupProfile) 
+            return;
+
+        $aContentInfo = $this->_oModule->serviceGetContentInfoById($oGroupProfile->getContentId());
+        $oAlert->aExtras['result'] = (CHECK_ACTION_RESULT_ALLOWED === $this->_oModule->$sFunc($aContentInfo)) ? true : false;
+    }
+
+    protected function processTimelineRepost ($oAlert, $iGroupProfileId)
+    {
+        if ($oAlert->aExtras['check_result'][CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return;
+
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if (!$oGroupProfile) 
+            return;
+
+        $aContentInfo = $this->_oModule->serviceGetContentInfoById($oGroupProfile->getContentId());
+        if (BX_DOL_PG_ALL == $aContentInfo[$this->_oModule->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']]) {
+            $oAlert->aExtras['check_result'][CHECK_ACTION_RESULT] = CHECK_ACTION_RESULT_ALLOWED;
+        }
+        else {
+            $oAlert->aExtras['check_result'][CHECK_ACTION_RESULT] = CHECK_ACTION_MESSAGE_NOT_ALLOWED;
+            $oAlert->aExtras['check_result'][CHECK_ACTION_MESSAGE] = _t('_sys_access_denied_to_private_content');
+        }
+    }
+}
+
+/** @} */

+ 1262 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfileModule.php

@@ -0,0 +1,1262 @@
+<?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);
+        $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 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()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = parent::serviceGetSearchableFieldsExtended();
+        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->fillFilters(array(
+			'perofileStatus' => BX_PROFILE_STATUS_PENDING
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+	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;
+    }
+
+    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 = (!empty($aParams['template']) ? $aParams['template'] : 'unit') . '.html';
+
+        return $this->_oTemplate->unit($aContentInfo, $bCheckPrivateContent, $sTemplate);
+    }
+
+    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 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('thumb', $iContentId);
+    }
+
+    public function serviceProfileIcon ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('icon', $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'];
+    }
+
+    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;
+        }
+
+        return $aRet;
+    }
+    
+    /**
+     * @see iBxDolProfileService::serviceCheckSpacePrivacy
+     */ 
+    public function serviceCheckSpacePrivacy($iContentId)
+    {
+        return $this->serviceCheckAllowedProfileView($iContentId);
+    }
+    
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileView
+     */ 
+    public function serviceCheckAllowedProfileView($iContentId)
+    {        
+        return $this->serviceCheckAllowedWithContent('View', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedPostInProfile
+     */ 
+    public function serviceCheckAllowedPostInProfile($iContentId, $iProfileIdPoster = false)
+    {
+        if (!$iContentId || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)))
+            return _t('_sys_request_page_not_found_cpt');
+        
+        return $this->serviceCheckAllowedViewForProfile($aContentInfo, false, $iProfileIdPoster);
+    }
+
+    /**
+     * 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,
+        );
+        $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 ();
+    }
+
+    public function serviceEntityCreate ($sDisplay = false)
+    {
+	    BxDolInformer::getInstance($this->_oTemplate)->setEnabled(false);
+
+	    return parent::serviceEntityCreate ($sDisplay);
+    }
+
+    public function serviceActAsProfile ()
+    {
+        return true;
+    }
+
+    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);
+    }
+
+    public function serviceBrowseRecentProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('recent', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    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);
+    }
+
+    public function serviceBrowseTopProfiles ($bDisplayEmptyMsg = false, $bAjaxPaginate = false)
+    {
+        return $this->_serviceBrowse ('top', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg, $bAjaxPaginate);
+    }
+
+    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 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))
+        );
+    }
+
+	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;
+    }
+    
+    /**
+     * 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;
+    }
+    
+    /**
+     * 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 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())
+    {
+        return $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'
+        ));
+    }
+
+    public function serviceGetTimelineProfileCover($aEvent, $aBrowseParams = array())
+    {
+        return $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'
+        ));
+    }
+
+    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
+
+    /**
+     * @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);
+    }
+
+    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);
+    }
+    
+    /**
+     * @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->checkAllowedView ($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! not as profile as ususal) 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->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! not as profile as ususal) 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->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;
+
+        // check ACL and owner (checked by account! not as profile as ususal)
+        $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->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;
+    }
+
+	/**
+     * @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->checkAllowedView ($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 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;
+
+        $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 _t('_sys_txt_access_denied');
+
+        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 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 $iProfileId) {
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+            if(!$oProfile)
+                continue;
+
+            $s .= $oProfile->getUnit();
+        }
+
+        // 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' => $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 _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('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']] : '',
+			'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);
+    }
+}
+
+/** @} */

+ 50 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfilePrivacy.php

@@ -0,0 +1,50 @@
+<?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 BxBaseModProfilePrivacy extends BxTemplPrivacy
+{
+    protected $MODULE;
+    protected $_oModule;
+
+    protected $_aPrivacyParticallyVisible = array ();
+
+    public function __construct($aOptions, $oTemplate = false)
+    {
+        parent::__construct($aOptions, $oTemplate);
+
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        $this->_aPrivacyParticallyVisible = array(BX_DOL_PG_FRIENDS);
+    }
+
+    public function isPartiallyVisible ($mixedPrivacy)
+    {
+        return in_array($mixedPrivacy, $this->_aPrivacyParticallyVisible);
+    }
+
+    public function getPartiallyVisiblePrivacyGroups ()
+    {
+        return $this->_aPrivacyParticallyVisible;
+    }
+
+    protected function getObjectInfo($sAction, $iObjectId)
+    {
+        $a = $this->_oDb->getObjectInfo($sAction, $iObjectId);
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iObjectId, $this->MODULE);
+        if (!$oProfile)
+            return false;
+
+        $a['owner_id'] = $oProfile->id();        
+        return $a;
+    }
+}
+
+/** @} */

+ 37 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/modules/base/profile/classes/BxBaseModProfileVote.php

@@ -0,0 +1,37 @@
+<?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 BxBaseModProfileVote extends BxTemplVote
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    function __construct($sSystem, $iId, $iInit = 1)
+    {
+        parent::__construct($sSystem, $iId, $iInit);        
+
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+    }
+
+    public function getObjectAuthorId($iObjectId = 0)
+    {
+        if(!BxDolService::call($this->_sModule, 'act_as_profile'))
+            return parent::getObjectAuthorId ($iObjectId);
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iObjectId ? $iObjectId : $this->getId(), $this->_sModule);
+        if(!$oProfile)
+            return 0;
+
+        return $oProfile->id();
+    }
+}
+
+/** @} */

+ 196 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/studio/classes/BxDolStudioDashboard.php

@@ -0,0 +1,196 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+class BxDolStudioDashboard extends BxTemplStudioPage
+{
+	protected $aBlocks;
+	protected $aItemsCache;
+	protected $aItemsHTools;
+
+    function __construct()
+    {
+        parent::__construct('dashboard');
+
+        $this->oDb = new BxDolStudioDashboardQuery();
+
+        $this->aBlocks = array(
+        	'space' => 'serviceGetBlockSpace',
+        	'htools' => 'serviceGetBlockHostTools',
+        );
+
+        $this->aItemsCache = array (
+		    array('name' => 'all'),
+		    array('name' => 'db'),
+		    array('name' => 'template'),
+		    array('name' => 'css'),
+		    array('name' => 'js')
+		);
+
+		$this->aItemsHTools = array (
+	    	'PHP' => 'requirementsPHP',
+			'MySQL' => 'requirementsMySQL',
+			'Web Server' => 'requirementsWebServer',
+		);
+
+        //--- Check actions ---//
+        if(($sAction = bx_get('dbd_action')) !== false) {
+            $sAction = bx_process_input($sAction);
+
+            $aResult = array('code' => 1, 'message' => _t('_adm_err_cannot_process_action'));
+            switch($sAction) {
+            	case 'get_block':
+            		$sValue = bx_get('dbd_value');
+					if($sValue === false)
+						break;
+
+					$sValue = bx_process_input($sValue);
+					if(!isset($this->aBlocks[$sValue]))
+						break;
+						
+					$aBlock = $this->{$this->aBlocks[$sValue]}(false);
+					if(!empty($aBlock['content']))
+            			$aResult = array('code' => 0, 'data' => $aBlock['content']);
+
+            		break;
+
+				case 'check_for_upgrade':
+					$aResult = array('code' => 0, 'data' => $this->getPageCodeVersionAvailable());
+                    break;
+
+            	case 'perform_upgrade':
+            		$oUpgrader = bx_instance('BxDolUpgrader');
+			        if(!$oUpgrader->prepare())
+			        	$aResult = array('code' => 1, 'message' => $oUpgrader->getError());
+			        else
+			        	$aResult = array('code' => 0, 'message' => _t('_adm_dbd_msg_upgrade_started', BX_DOL_URL_STUDIO));
+					break;
+
+				case 'clear_cache':
+					$sValue = bx_get('dbd_value');
+					if($sValue === false)
+						break;
+
+					$sValue = bx_process_input($sValue);
+
+					$oCacheUtilities = BxDolCacheUtilities::getInstance();
+
+					switch ($sValue) {
+				        case 'all':
+				        	$aResult = false;
+				            foreach($this->aItemsCache as $aItem) {
+				            	if($aItem['name'] == 'all')
+				            		continue;
+
+				                $aResultClear = $oCacheUtilities->clear($aItem['name']);
+				                if($aResultClear === false)
+				                	continue;
+
+								$aResult = $aResultClear;
+				                if(isset($aResult['code']) && $aResult['code'] != 0)
+									break;
+				            }
+				            break;
+
+				        case 'db':
+				        case 'template':
+				        case 'css':
+				        case 'js':
+				            $aResult = $oCacheUtilities->clear($sValue);
+				            break;
+
+				        default:
+				            $aResult = array('code' => 1, 'message' => _t('_Error Occured'));
+				    }
+
+				    if($aResult === false)
+				    	$aResult['data'] = MsgBox(_t('_adm_dbd_msg_c_all_disabled'));
+				    else if(isset($aResult['code']) && $aResult['code'] == 0)
+				        $aResult['data'] = $this->getCacheChartData(false);
+
+					break;
+
+				case 'permissions':                    
+                    $oAdmTools = new BxDolStudioTools();
+
+                    header( 'Content-type: text/html; charset=utf-8' );
+                    echo $oAdmTools->generateStyles();
+                    $oAdmTools->checkPermissions();
+                    exit;
+
+				case 'server_audit':
+					$oAudit = new BxDolStudioToolsAudit();
+            
+                    header( 'Content-type: text/html; charset=utf-8' );
+					echo $oAudit->generate();
+					exit;
+            }
+
+            if(!empty($aResult['message'])) {
+                $aResult['message'] = BxDolStudioTemplate::getInstance()->parseHtmlByName('page_action_result.html', array('content' => $aResult['message']));
+                $aResult['message'] = BxTemplStudioFunctions::getInstance()->transBox('', $aResult['message']);
+            }
+
+            echo json_encode($aResult);
+            exit;
+        }
+    }
+
+    protected function getDbSize()
+    {
+        $iTotalSize = 0;
+        $oDb = BxDolDb::getInstance();
+
+        $aTables = $oDb->getAll('SHOW TABLE STATUS');
+        foreach($aTables as $aTable)
+            $iTotalSize += $aTable['Data_length'] + $aTable['Index_length'];
+
+        return $iTotalSize;
+    }
+
+    protected function getFolderSize($sPath)
+    {
+        $iTotalSize = 0;
+        $aFiles = scandir($sPath);
+
+        $sPath = rtrim($sPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+        foreach($aFiles as $sFile) {
+            if(is_dir($sPath . $sFile))
+                  if($sFile != '.' && $sFile != '..')
+                      $iTotalSize += $this->getFolderSize($sPath . $sFile);
+            else
+                  $iTotalSize += filesize($sPath . $sFile);
+        }
+
+        return $iTotalSize;
+    }
+
+    protected function getCacheChartData($bAsString = true)
+    {
+		$oCacheUtilities = BxDolCacheUtilities::getInstance();
+
+    	$aChartData = array();
+    	foreach($this->aItemsCache as $aItem) {
+    		if($aItem['name'] == 'all')
+	            continue;
+
+	        $iSize = $oCacheUtilities->size($aItem['name']);
+	        if($iSize === false)
+	        	continue;
+
+	        $aChartData[] = array(bx_js_string(_t('_adm_dbd_txt_c_' . $aItem['name']), BX_ESCAPE_STR_APOS), array('v' => $iSize, 'f' => bx_js_string(_t_format_size($iSize))));
+    	}
+
+    	if(empty($aChartData))
+    		return false;
+
+    	return $bAsString ? json_encode($aChartData) : $aChartData;
+    }
+}
+
+/** @} */

+ 391 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/studio/classes/BxDolStudioStore.php

@@ -0,0 +1,391 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+bx_import('BxDolStudioInstallerUtils');
+
+define('BX_DOL_STUDIO_STR_TYPE_DEFAULT', 'downloaded');
+
+class BxDolStudioStore extends BxTemplStudioPage
+{
+    protected $sPage;
+    protected $aContent;
+    protected $sBaseUrl;
+
+    protected $iClient;
+    protected $sClientKey; 
+
+    protected $sStoreDataUrlPublic;
+    protected $bAuthAccessUpdates;
+
+    function __construct($sPage = "")
+    {
+        parent::__construct('store');
+
+        $this->oDb = new BxDolStudioStoreQuery();
+
+        $this->sBaseUrl = BX_DOL_URL_STUDIO . 'store.php';
+
+        $this->sStoreDataUrlPublic = BxDolStudioInstallerUtils::getInstance()->getStoreDataUrl();
+        $this->bAuthAccessUpdates = false;
+
+        $this->sPage = BX_DOL_STUDIO_STR_TYPE_DEFAULT;
+        if(is_string($sPage) && !empty($sPage))
+            $this->sPage = $sPage;
+
+        $this->initClient();
+
+        //--- Check actions ---//
+        if(($sAction = bx_get('str_action')) !== false) {
+            $sAction = bx_process_input($sAction);
+
+            $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_mod_err_cannot_process_action'));
+            switch($sAction) {
+                case 'get-file':
+                    $iFileId = (int)bx_get('str_id');
+                    $aResult = $this->getFile($iFileId);
+                    break;
+
+                case 'get-product':
+                    $sModuleName = bx_process_input(bx_get('str_id'));
+                    $aResult = $this->getProduct($sModuleName);
+                    break;
+
+                case 'get-update':
+                    $sModuleName = bx_process_input(bx_get('str_id'));
+                    $aResult = $this->getUpdate($sModuleName);
+                    break;
+
+				case 'get-update-and-install':
+                    $sModuleName = bx_process_input(bx_get('str_id'));
+                    $aResult = $this->getUpdate($sModuleName, true);
+                    break;
+
+                case 'get-products-by-type':
+                    $this->sPage = bx_process_input(bx_get('str_value'));
+
+                    $sContent = $this->getPageCode();
+                    if(!empty($sContent))
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'content' => $sContent);
+                    else
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_act_err_failed_page_loading'));
+                    break;
+
+                case 'get-products-by-page':
+                    $this->sPage = bx_process_input(bx_get('str_type'));
+
+                    $sContent = $this->getPageContent();
+                    if(!empty($sContent))
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'content' => $sContent);
+                    else
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_act_err_failed_page_loading'));
+                    break;
+
+                case 'add-to-cart':
+                    $iVendor = (int)bx_get('str_vendor');
+                    $iItem = (int)bx_get('str_item');
+                    $iItemCount = 1;
+
+                    if(empty($iVendor) || empty($iItem)) {
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_err_modules_cannot_add_to_cart'));
+                        break;
+                    }
+
+                    BxDolStudioCart::getInstance()->add($iVendor, $iItem, $iItemCount);
+                    $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => _t('_adm_msg_modules_success_added_to_cart'));
+                    break;
+
+                case 'delete-from-cart':
+                    $iVendor = (int)bx_get('str_vendor');
+                    $iItem = (int)bx_get('str_item');
+
+                    if(empty($iVendor)) {
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_err_modules_cannot_delete_from_cart'));
+                        break;
+                    }
+
+                    BxDolStudioCart::getInstance()->delete($iVendor, $iItem);
+                    $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => '');
+                    break;
+
+                case 'checkout-cart':
+                    $iVendor = (int)bx_get('str_vendor');
+                    if(empty($iVendor)) {
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_err_modules_cannot_checkout_empty_vendor'));
+                        break;
+                    }
+
+                    $sLocation = $this->checkoutCart($iVendor);
+                    $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => '', 'redirect' => $sLocation);
+                    break;
+
+				case 'subscribe':
+                    $iVendor = (int)bx_get('str_vendor');
+                    $iItem = (int)bx_get('str_item');
+                    if(empty($iVendor) || empty($iItem)) {
+                        $aResult = array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_err_modules_cannot_subscribe'));
+                        break;
+                    }
+
+                    $sLocation = $this->subscribe($iVendor, $iItem);
+                    $aResult = array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => '', 'redirect' => $sLocation);
+                    break;
+
+                case 'install':
+                    $sValue = bx_process_input(bx_get('str_value'));
+                    if(empty($sValue))
+                        break;
+
+                    $aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'install', array('auto_enable' => true, 'html_response' => true));
+                    break;
+
+                case 'update':
+                    $sValue = bx_process_input(bx_get('str_value'));
+                    if(empty($sValue))
+                        break;
+
+                    $aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'update', array('html_response' => true));
+                    break;
+
+                case 'delete':
+                    $sValue = bx_process_input(bx_get('str_value'));
+                    if(empty($sValue))
+                        break;
+
+                    $aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'delete', array('html_response' => true));
+                    break;
+            }
+
+            if(!empty($aResult['message'])) {
+                $oTemplate = BxDolStudioTemplate::getInstance();
+
+                $aResult['message'] = $oTemplate->parseHtmlByName('popup_chain.html', array(
+                    'html_id' => 'mod_action_result',
+                    'bx_repeat:items' => array(array(
+                        'bx_if:show_as_hidden' => array(
+                            'condition' => false,
+                            'content' => array(),
+                        ),
+                        'item' => $oTemplate->parseHtmlByName('str_notification.html', array(
+                            'content' => $aResult['message']
+                        )),
+                        'bx_if:show_previous' => array(
+                            'condition' => false,
+                            'content' => array(
+                                'onclick_previous' => ''
+                            )
+                        ),
+                        'bx_if:show_close' => array(
+                           'condition' => false,
+                            'content' => array(
+                                'onclick_close' => ''
+                            )
+                        )
+                    ))
+                ));
+            }
+
+            echo json_encode($aResult);
+            exit;
+        }
+    }
+
+    protected function initClient()
+    {
+        $this->iClient = BxDolStudioOAuth::getAuthorizedClient();
+        if(!empty($this->iClient))
+            $this->sClientKey = $this->oDb->getParam('sys_oauth_key');
+    }
+
+    protected function authorizeClient()
+    {
+        $mixedResult = BxDolStudioInstallerUtils::getInstance()->getAccessObject(true)->doAuthorize();
+        if($mixedResult === true)
+            $this->initClient();
+
+        return $mixedResult;
+    }
+
+    protected function loadGoodies()
+    {
+        $iPerPage = 3;
+        $aProducts = array();
+
+        $oJson = BxDolStudioJson::getInstance();
+
+        // Load Featured
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_featured',
+            'actions' => array(
+                array('name' => 'featured', 'caption' => '_adm_action_cpt_see_all', 'url' => $this->getBaseUrl('featured'))
+            ),
+            'items' => $oJson->load($this->sStoreDataUrlPublic . 'json_browse_featured', array('start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey))
+        );
+
+        // Load Latest
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_latest',
+            'actions' => array(
+                array('name' => 'latest', 'caption' => '_adm_action_cpt_see_all', 'url' => $this->getBaseUrl('latest'))
+            ),
+            'items' => $oJson->load($this->sStoreDataUrlPublic . 'json_browse_latest', array('start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey))
+        );
+
+        // Load Popular
+        $aProducts[] = array(
+            'caption' => '_adm_block_cpt_popular',
+            'actions' => array(
+                array('name' => 'popular', 'caption' => '_adm_action_cpt_see_all', 'url' => $this->getBaseUrl('popular'))
+            ),
+            'items' => $oJson->load($this->sStoreDataUrlPublic . 'json_browse_popular', array('start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey))
+        );
+
+        return $aProducts;
+    }
+
+    protected function loadLatest($iStart, $iPerPage)
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_latest', array('start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadFeatured($iStart, $iPerPage)
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_featured', array('start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadPopular($iStart, $iPerPage)
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_popular', array('start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadCategories()
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_categories', array('client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+	protected function loadCategory($iCategory, $iStart, $iPerPage)
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_by_category', array('value' => $iCategory, 'start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadTags()
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_tags', array('client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadTag($sTag, $iStart, $iPerPage)
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_by_tag', array('value' => $sTag, 'start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadSearch($sKeyword, $iStart, $iPerPage)
+    {
+        return BxDolStudioJson::getInstance()->load($this->sStoreDataUrlPublic . 'json_browse_by_keyword', array('value' => $sKeyword, 'start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    protected function loadPurchases()
+    {
+        return BxDolStudioInstallerUtils::getInstance()->checkModules(true);
+    }
+
+    protected function loadUpdates()
+    {
+        return BxDolStudioInstallerUtils::getInstance()->checkUpdates($this->bAuthAccessUpdates);
+    }
+
+    protected function loadCheckout()
+    {
+        $oJson = BxDolStudioJson::getInstance();
+
+        $aVendors = BxDolStudioCart::getInstance()->parseByVendor();
+
+        $aResult = array();
+        foreach($aVendors as $sVendor => $aItems) {
+            $aIds = $aCounts = array();
+            foreach($aItems as $aItem) {
+                $aIds[] = $aItem['item_id'];
+                $aCounts[$aItem['item_id']] = $aItem['item_count'];
+            }
+
+            $aProducts = $oJson->load($this->sStoreDataUrlPublic . 'json_browse_selected', array('products' => base64_encode(serialize($aIds))));
+            if(!empty($aProducts))
+                $aResult[$sVendor] = array(
+                    'ids' => $aIds,
+                    'counts' => $aCounts,
+                    'products' => $aProducts
+                );
+        }
+
+        return $aResult;
+    }
+
+    protected function loadDownloaded()
+    {
+        $oInstallerUtils = BxDolStudioInstallerUtils::getInstance();
+
+        return array(
+            'modules' => $oInstallerUtils->getModules(),
+            'updates' => $oInstallerUtils->getUpdates()
+        );
+    }
+
+    protected function loadProduct($sModuleName)
+    {
+        $oJson = BxDolStudioJson::getInstance();
+
+        return $oJson->load($this->sStoreDataUrlPublic . 'json_get_product_by_name', array('value' => $sModuleName, 'client' => $this->iClient, 'key' => $this->sClientKey));
+    }
+
+    /*
+     * Load package (module, update) using OAuth authorization.
+     */
+    protected function loadFile($iFileId)
+    {
+        return BxDolStudioInstallerUtils::getInstance()->downloadFileAuthorized($iFileId);
+    }
+
+    /*
+     * Load update's package publicly.
+     */
+	protected function loadUpdate($sModuleName, $bAutoUpdate = false)
+    {
+        return BxDolStudioInstallerUtils::getInstance()->downloadUpdatePublic($sModuleName, $bAutoUpdate);
+    }
+
+    protected function getBaseUrl($mixedParams = array())
+    {
+        if(is_string($mixedParams))
+            $mixedParams = array('page' => $mixedParams);
+
+        return bx_append_url_params($this->sBaseUrl, $mixedParams);
+    } 
+
+    private function checkoutCart($iVendor)
+    {
+        $oCart = BxDolStudioCart::getInstance();
+
+        $aItems = $oCart->getByVendor($iVendor);
+        if(empty($aItems) || !is_array($aItems))
+            return false;
+
+        $aIds = array();
+        foreach($aItems as $aItem)
+            $aIds[] = $aItem['item_id'];
+
+        $sSid = bx_site_hash();
+        return $this->sStoreDataUrlPublic . 'purchase/' . $iVendor . '?sid=' . $sSid . '&products=' . base64_encode(implode(',', $aIds));
+    }
+
+	private function subscribe($iVendor, $iItem)
+    {
+        $sSid = bx_site_hash();
+        return $this->sStoreDataUrlPublic . 'subscribe/' . $iVendor . '/'. $iItem . '?sid=' . $sSid;
+    }
+}
+
+/** @} */

+ 1055 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/studio/template/scripts/BxBaseStudioStore.php

@@ -0,0 +1,1055 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioStore extends BxDolStudioStore
+{
+    protected $iPerPageDefault = 24;
+
+    function __construct($sPage = "")
+    {
+        parent::__construct($sPage);
+    }
+
+    function getPageCss()
+    {
+        return array_merge(parent::getPageCss(), array('modules.css', 'store.css', 'store-media-tablet.css', 'store-media-desktop.css'));
+    }
+
+    function getPageJs()
+    {
+    	BxDolStudioTemplate::getInstance()->addJsTranslation(array('_adm_btn_queued_submit'));
+        return array_merge(parent::getPageJs(), array('fancybox/jquery.fancybox.pack.js', 'store.js'));
+    }
+
+    function getPageJsObject()
+    {
+        return 'oBxDolStudioStore';
+    }
+
+    function getPageMenu($aMenu = array(), $aMarkers = array())
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aMenu = array();
+        $aMenuItems = array(
+	        'goodies' => array('icon' => 'home'),
+        	'latest' => array('icon' => 'th-large'),
+	        'featured' => array('icon' => 'thumbs-up'),
+        	'popular' => array('icon' => 'star'),
+        	'categories' => array('icon' => 'tag'),
+        	'search' => array('icon' => 'search'),
+	        'purchases' => array('icon' => 'shopping-cart'), 
+	        'updates' => array('icon' => 'sync'), 
+	        'checkout' => array('icon' => 'credit-card'), 
+	        'downloaded' => array('icon' => 'list')
+        );
+        foreach($aMenuItems as $sMenuItem => $aItem)
+            $aMenu[] = array(
+                'name' => $sMenuItem,
+                'icon' => $aItem['icon'],
+                'link' => $this->getBaseUrl($sMenuItem),
+                'title' => _t('_adm_lmi_cpt_' . $sMenuItem),
+                'selected' => $sMenuItem == $this->sPage
+            );
+
+        $iCounter = BxDolStudioCart::getInstance()->getCount();
+
+        $aMarkers = array(
+            'checkout_counter' => $oTemplate->parseHtmlByName('menu_side_counter.html', array(
+                'bx_if:hide_counter' => array(
+                    'condition' => $iCounter <= 0,
+                    'content' => array()
+                ),
+                'counter' => $iCounter
+            ))
+        );
+
+        return parent::getPageMenu($aMenu, $aMarkers);
+    }
+
+    function getPageCode($bHidden = false)
+    {
+        $sMethod = 'get' . ucfirst($this->sPage) . 'List';
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $this->$sMethod();
+    }
+
+    function getPageContent()
+    {
+        $sMethod = 'get' . ucfirst($this->sPage) . 'List';
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $this->$sMethod(false);
+    }
+
+    protected function getGoodiesList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+	        $aProducts = $this->loadGoodies();
+
+	        $sContent = "";
+	        $sContent .= $this->getBlocksLine(array(
+                array(
+    	            'caption' => '_adm_block_cpt_categories',
+                	'actions' => array(),
+                    'items' => $this->getCategoriesList(false)
+                ),
+                array(
+    	            'caption' => '_adm_block_cpt_tags',
+                	'actions' => array(),
+                    'items' => $this->getTagsList(false)
+                )
+            ));        
+
+	        foreach($aProducts as $aBlock) {
+	            $aBlock['items'] = $oTemplate->parseHtmlByName('str_products.html', array(
+	                'list' => $this->displayProducts($aBlock['items']),
+	                'paginate' => ''
+	            ));
+
+	            $sContent .= $this->getBlockCode($aBlock);
+	        }
+        }
+        else
+        	$sContent = $this->getBlockCode(array(
+                'caption' => '_adm_block_cpt_goodies',
+                'items' => $mixedResult,
+            ));
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $sContent
+        ));
+    }
+
+    protected function getLatestList($bWrapInBlock = true)
+    {
+        return $this->getBrowsingList('latest', $bWrapInBlock);
+    }
+
+    protected function getFeaturedList($bWrapInBlock = true)
+    {
+        return $this->getBrowsingList('featured', $bWrapInBlock);
+    }
+
+    protected function getPopularList($bWrapInBlock = true)
+    {
+        return $this->getBrowsingList('popular', $bWrapInBlock);
+    }
+
+    protected function getBrowsingList($sType, $bWrapInBlock = true)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+	        $iStart = (int)bx_get('str_start');
+	        $iPerPage = (int)bx_get('str_per_page');
+	        if(empty($iPerPage))
+	            $iPerPage = $this->iPerPageDefault;
+
+	        $aProducts = $this->{'load' . bx_gen_method_name($sType)}($iStart, $iPerPage + 1);
+
+	        $oPaginate = new BxTemplPaginate(array(
+	            'start' => $iStart,
+	            'per_page' => $iPerPage,
+	            'on_change_page' => $sJsObject . ".changePagePaginate(this, '" . $sType . "', null, {start}, {per_page})"
+	        ));
+	        $oPaginate->setNumFromDataArray($aProducts);
+
+	        $sContent = $oTemplate->parseHtmlByName('str_products.html', array(
+	            'list' => $this->displayProducts($aProducts),
+	            'paginate' => $oPaginate->getSimplePaginate()
+	        ));
+        }
+        else 
+            $sContent = $mixedResult;
+
+		if(!$bWrapInBlock)
+			return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_' . $sType,
+				'items' => $sContent
+			))
+        ));
+    }
+
+    protected function getCategoriesList($bWrapInBlock = true)
+    {
+        return $this->getLabelsList('categories', $bWrapInBlock);
+    }
+
+    protected function getCategoryList($bWrapInBlock = true)
+    {
+        $sKey = 'value';
+        if(bx_get($sKey) === false)
+            $sKey = 'str_value';
+
+        $aValue = array();
+        $aValue['value'] = (int)bx_get($sKey);
+        if(bx_get('title') !== false)
+            $aValue['title'] = bx_process_input(urldecode(bx_get('title')));
+
+        return $this->getLabelList('category', $aValue, $bWrapInBlock);
+    }
+
+    protected function getTagsList($bWrapInBlock = true)
+    {
+        return $this->getLabelsList('tags', $bWrapInBlock);
+    }
+
+    protected function getTagList($bWrapInBlock = true)
+    {
+        $sKey = 'value';
+        if(bx_get($sKey) === false)
+            $sKey = 'str_value';
+
+        return $this->getLabelList('tag', bx_process_input(bx_get($sKey)), $bWrapInBlock);
+    }
+
+    protected function getLabelsList($sType, $bWrapInBlock = true)
+    {
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+            $aLabels = $this->{'load' . bx_gen_method_name($sType)}();
+            $sContent = $this->{'display' . bx_gen_method_name($sType)}($aLabels);
+        }
+        else 
+            $sContent = $mixedResult;
+
+    	if(!$bWrapInBlock)
+			return $sContent;
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_' . $sType,
+				'items' => $sContent
+			))
+        ));
+    }
+
+    protected function getLabelList($sType, $mixedValue, $bWrapInBlock = true)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $iStart = (int)bx_get('str_start');
+        $iPerPage = (int)bx_get('str_per_page');
+        if(empty($iPerPage))
+            $iPerPage = $this->iPerPageDefault;
+
+        $bArray = is_array($mixedValue);
+        $sValue = $bArray ? $mixedValue['value'] : $mixedValue;
+        $sTitle = $bArray && !empty($mixedValue['title']) ? $mixedValue['title'] : $sValue;
+
+		$sMethod = 'load' . bx_gen_method_name($sType);
+        $aProducts = $this->$sMethod($sValue, $iStart, $iPerPage + 1);
+
+        $oPaginate = new BxTemplPaginate(array(
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'on_change_page' => $sJsObject . ".changePagePaginate(this, '" . $sType . "', '" . $sValue . "', {start}, {per_page})"
+        ));
+        $oPaginate->setNumFromDataArray($aProducts);
+
+        $sContent = $oTemplate->parseHtmlByName('str_products.html', array(
+            'list' => $this->displayProducts($aProducts),
+            'paginate' => $oPaginate->getSimplePaginate()
+        ));
+
+        if(!$bWrapInBlock)
+            return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $this->getBlockCode(array(
+				'caption' => array('_adm_block_cpt_' . $sType, $sTitle),
+				'items' => $sContent,
+			))
+        ));
+    }
+
+    protected function getSearchList($bWrapInBlock = true)
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $mixedResult = $this->authorizeClient();
+        if($mixedResult === true) {
+            //--- Search form
+            $aForm = array(
+                'form_attrs' => array(
+                    'id' => 'adm-str-search-form',
+                    'name' => 'adm-str-search-form',
+                    'action' => '',
+                    'method' => 'post'
+                ),
+                'params' => array(
+                    'db' => array(
+                        'table' => '',
+                        'key' => '',
+                        'uri' => '',
+                        'uri_title' => '',
+                        'submit_name' => 'search'
+                    ),
+                ),
+                'inputs' => array(
+                    'page' => array(
+                        'type' => 'hidden',
+                        'name' => 'page',
+                        'value' => $this->sPage
+                    ),
+                    'keyword' => array(
+                        'type' => 'text',
+                        'name' => 'keyword',
+                        'caption' => '',
+                        'value' => '',
+                        'attrs' => array(
+                            'placeholder' => bx_html_attribute(_t('_sys_search_placeholder'))
+                        ),
+                        'db' => array (
+                            'pass' => 'Xss',
+                        )
+                    ),
+                    'search' => array(
+                        'type' => 'submit',
+                        'name' => 'search',
+                        'value' => _t('_adm_btn_store_search'),
+                    )
+                )
+            );
+    
+            $oForm = new BxTemplStudioFormView($aForm);
+            $oForm->initChecker();
+    
+            $sResults = '';
+            if($oForm->isSubmittedAndValid()) {
+                $sKeyword = $oForm->getCleanValue('keyword');
+
+                $iStart = (int)bx_get('str_start');
+    	        $iPerPage = (int)bx_get('str_per_page');
+    	        if(empty($iPerPage))
+    	            $iPerPage = 999;
+    
+    	        $aProducts = $this->loadSearch($sKeyword, $iStart, $iPerPage + 1);
+	            $sResults = empty($aProducts) || !is_array($aProducts) ? MsgBox(_t('_Empty')) : $oTemplate->parseHtmlByName('str_products.html', array(
+    	            'list' => $this->displayProducts($aProducts),
+    	            'paginate' => ''
+    	        ));
+            }         
+
+            $sContent = $oTemplate->parseHtmlByName('str_search.html', array(
+                'form' => $oForm->getCode(),
+                'bx_if:show_results' => array(
+                    'condition' => !empty($sResults),
+                    'content' => array(
+                        'results' => $sResults
+                    )
+                )
+            ));
+        }
+        else 
+            $sContent = $mixedResult;
+
+        if(!$bWrapInBlock)
+			return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_search',
+				'items' => $sContent
+			))
+        ));
+    }
+
+    protected function getPurchasesList()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProducts = $this->loadPurchases();
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_purchases',
+				'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $this->displayProducts($aProducts),
+		            'paginate' => ''
+		        ))
+			))
+        ));
+    }
+
+    protected function getUpdatesList()
+    {
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aUpdates = $this->loadUpdates();
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_updates',
+				'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $this->displayUpdates($aUpdates),
+		            'paginate' => ''
+		        ))
+			))
+        ));
+    }
+
+    protected function getCheckoutList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        //--- Empty cart by vendor if the payment was accepted.
+        $mixedVendor = bx_get('vendor');
+        $mixedProducts = bx_get('products');
+        if($mixedVendor !== false && $mixedProducts !== false) {
+            $oCart = BxDolStudioCart::getInstance();
+
+            $aProducts = explode(',', base64_decode($mixedProducts));
+            foreach($aProducts as $iId)
+                $oCart->delete($mixedVendor, $iId);
+
+            return $this->getMessage('_adm_block_cpt_checkout', '_adm_msg_modules_success_checkouted');
+        }
+
+        $aVendors = $this->loadCheckout();
+        if(empty($aVendors))
+            return $this->getMessage('_adm_block_cpt_checkout', '_Empty');        
+
+        $sContent = '';
+        foreach($aVendors as $iVendor => $aInfo) {
+            $fTotal = 0;
+            $sVendor = $sCurrency = '';
+            foreach($aInfo['products'] as $aProduct) {
+                $iCount = isset($aInfo['counts'][$aProduct['id']]) ? (int)$aInfo['counts'][$aProduct['id']] : 1;
+                $fTotal += $iCount * $aProduct['price_single'];
+
+                if($sVendor == '' && isset($aProduct['author_name']))
+                    $sVendor = $aProduct['author_name'];
+
+				if($sCurrency == '' && isset($aProduct['author_currency_sign']))
+                    $sCurrency = $aProduct['author_currency_sign'];
+            }
+
+            $aMenu = array(
+                array('id' => 'checkout-' . $iVendor, 'name' => 'checkout-' . $iVendor, 'link' => 'javascript:void(0)', 'onclick' => $sJsObject . ".checkoutCart(" . $iVendor . ", this);", 'target' => '_self', 'title' => '_adm_action_cpt_checkout', 'active' => 1),
+                array('id' => 'delete-all-' . $iVendor, 'name' => 'delete-all-' . $iVendor, 'link' => 'javascript:void(0)', 'onclick' => $sJsObject . ".deleteAllFromCart(" . $iVendor . ", this)", 'target' => '_self', 'title' => '_adm_action_cpt_delete_all', 'active' => 1)
+            );
+	        $oMenu = new BxTemplMenu(array('object'=> 'adm-std-checkout', 'template' => 'menu_buttons_hor.html', 'menu_items' => $aMenu));
+
+	        $sContent .= $this->getBlockCode(array(
+                'caption' => _t('_adm_block_cpt_checkout_by_vendor_csign', $sVendor, $sCurrency, $fTotal),
+                'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $this->displayProducts($aInfo['products'], array('is_shopping_cart' => true, 'counts' => $aInfo['counts'])),
+		            'paginate' => ''
+		        )),
+                'panel_bottom' => $oMenu->getCode()
+            ));
+        }
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+            'content' => $sContent
+        ));
+    }
+
+    protected function getDownloadedList()
+    {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sContent = $sModules = $sUpdates = "";
+        $aProducts = $this->loadDownloaded();
+
+        //--- Prepare modules.
+        foreach($aProducts['modules'] as $aModule) {
+        	$sIcon = BxDolStudioUtils::getModuleIcon($aModule, 'store');
+        	$bIcon = strpos($sIcon, '.') !== false;
+
+        	$bInstalled = $aModule['installed'];
+        	$bQueued = !$bInstalled && $this->oDb->isQueued('action', $aModule['dir']);
+
+            $sModules .= $oTemplate->parseHtmlByName('str_product_v1.html', array(
+                'js_object' => $sJsObject,
+             	'name' => $aModule['name'],
+            	'bx_if:no_icon' => array (
+	                'condition' => !$bIcon,
+	                'content' => array(
+            			'icon' => $sIcon
+            		),
+	            ),
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array(
+	                	'icon_url' => $sIcon,
+	            	),
+	            ),
+                'title' => $aModule['title'],
+                'vendor' => $aModule['vendor'],
+                'version' => $aModule['version'],
+                'dir' => $aModule['dir'],
+                'bx_if:hide_install' => array(
+                    'condition' => $bInstalled || $bQueued,
+                    'content' => array()
+                ),
+                'bx_if:hide_queued' => array(
+                    'condition' => !$bQueued,
+                    'content' => array()
+                ),
+                'bx_if:hide_installed' => array(
+                    'condition' => !$bInstalled || $bQueued,
+                    'content' => array()
+                )
+            ));
+        }
+
+        //--- Prepare updates.
+        foreach($aProducts['updates'] as $aUpdate) {
+        	$sIcon = BxDolStudioUtils::getModuleIcon(array('type' => $aUpdate['module_type'], 'name' => $aUpdate['module_name'], 'dir' => $aUpdate['module_dir']), 'store');
+        	$bIcon = strpos($sIcon, '.') !== false;
+
+            $sUpdates .= $oTemplate->parseHtmlByName('str_update_v1.html', array(
+                'js_object' => $sJsObject,
+            	'name' => $aUpdate['module_name'],
+            	'bx_if:no_icon' => array (
+	                'condition' => !$bIcon,
+	                'content' => array(
+            			'icon' => $sIcon
+            		),
+	            ),
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array(
+	                	'icon_url' => $sIcon,
+	            	),
+	            ),
+                'title' => $aUpdate['title'],
+                'vendor' => $aUpdate['vendor'],
+                'versions' => _t('_adm_str_txt_update_from_to', $aUpdate['version_from'], $aUpdate['version_to']),
+                'dir' => $aUpdate['dir']
+            ));
+        }
+
+        if(!empty($sModules))
+        	$sContent .= $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_downloaded_modules',
+				'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $sModules,
+		            'paginate' => ''
+		        )),
+			));
+
+        if(!empty($sUpdates))
+            $sContent .= $this->getBlockCode(array(
+				'caption' => '_adm_block_cpt_downloaded_updates',
+				'items' => $oTemplate->parseHtmlByName('str_products.html', array(
+		            'list' => $sUpdates,
+		            'paginate' => ''
+		        )),
+			));
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+            'js_object' => $sJsObject,
+        	'content' => $sContent
+        ));
+    }
+
+    protected function getMessage($sCaption, $sContent, $aActions = array())
+    {
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('store.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'content' => $this->getBlockCode(array(
+				'caption' => $sCaption,
+        		'actions' => $aActions,
+				'items' => MsgBox(_t($sContent))
+			))
+        ));
+    }
+
+    protected function getProduct($sModuleName)
+    {
+        $bShowRating = true;
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProduct = $this->loadProduct($sModuleName);
+        if(empty($aProduct) || !is_array($aProduct))
+            return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => (!empty($aProduct) ? $aProduct : _t('_adm_str_err_no_product_info')));
+
+		$aDownloaded = $this->getDownloadedModules(false);
+
+        $bFree = (int)$aProduct['is_free'] == 1;
+        $bPurchased = (int)$aProduct['is_purchased'] == 1;
+
+        $bPurchaseSingle = !$bFree && !$bPurchased && (float)$aProduct['price_single'] != 0;
+		$bInCart = $bPurchaseSingle && BxDolStudioCart::getInstance()->exists($aProduct['author_id'], $aProduct['id']);
+
+		$bPurchaseRecurring = !$bFree && !$bPurchased && (float)$aProduct['price_recurring'] != 0;
+
+        $bDownloadable = (int)$aProduct['is_file'] == 1;
+        $bDownloaded = array_key_exists($sModuleName, $aDownloaded);
+        $bDownload = ($bFree || $bPurchased) && $bDownloadable;
+
+        $bDiscount = !empty($aProduct['discount_single']);
+        $sVersion = $bDownloaded && version_compare($aDownloaded[$sModuleName]['version'], $aProduct['version']) != 0 ? _t('_adm_str_txt_pv_version_mask', $aDownloaded[$sModuleName]['version'], $aProduct['version']) : $aProduct['version'];
+
+        $iScreenshots = 0;
+        $aScreenshots = array();
+        $bScreenshots = is_array($aProduct['screenshots']) && !empty($aProduct['screenshots']);
+        if($bScreenshots) {
+            $iScreenshots = count($aProduct['screenshots']);
+            foreach($aProduct['screenshots'] as $aScreenshot)
+                $aScreenshots[] = array(
+                    'view_url' => $aProduct['url'],
+                    'image_url' => $aScreenshot['big']
+                );
+        }
+
+		$bTmplVarsRate = $bShowRating;
+		if($bTmplVarsRate)
+		    $aTmplVarsRate = array(
+		        'rate' => $this->getVoteStars($aProduct)
+		    );
+
+		$aTmplVarsVotes = array();
+		$bTmplVarsVotes = $bShowRating && (int)$aProduct['votes_cnt'] > 0;
+		if($bTmplVarsVotes)
+		    $aTmplVarsVotes = array(
+		        'count' => (int)$aProduct['votes_cnt']
+		    );
+
+        $sContent = $oTemplate->parseHtmlByname('str_product_view.html', array(
+            'id' => $aProduct['id'],
+            'title' => $aProduct['title'],
+            'url' => $aProduct['url'],
+            'author_name' => $aProduct['author_name'],
+            'author_url' => $aProduct['author_url'],
+            'version' => $sVersion,
+        	'bx_if:show_price_single' => array(
+        		'condition' => $bFree || (float)$aProduct['price_single'] != 0,
+        		'content' => array(
+        			'price_single' => !$bFree ? _t('_adm_str_txt_price_single', $aProduct['author_currency_sign'], $aProduct['price_single']) : _t('_adm_str_txt_price_free'),
+		        	'bx_if:show_discount' => array(
+		                'condition' => !$bFree && $bDiscount,
+		                'content' => array(
+		                    'discount_single' => $bDiscount ? _t('_adm_str_txt_pv_discount_off_csign', $aProduct['discount_single']['percent'], $aProduct['author_currency_sign'], $aProduct['discount_single']['save']) : ''
+		                )
+		            )
+        		)
+        	),
+        	'bx_if:show_price_recurring' => array(
+				'condition' => (float)$aProduct['price_recurring'] != 0,
+        		'content' => array(
+					'price_recurring' => _t('_adm_str_txt_price_recurring', $aProduct['author_currency_sign'], $aProduct['price_recurring'], _t('_adm_str_txt_per_' . $aProduct['duration_recurring']))
+        		)
+        	),
+        	'bx_if:show_rate' => array(
+                'condition' => $bTmplVarsRate,
+                'content' => $aTmplVarsRate
+            ),
+            'bx_if:show_votes' =>  array(
+                'condition' => $bTmplVarsVotes,
+                'content' => $aTmplVarsVotes
+            ),
+            'category' => $aProduct['category'],
+            'category_url' => $aProduct['category_url'],
+            'tags' => implode(', ', explode(',', $aProduct['tags'])),
+            'reviews' => _t('_adm_str_txt_pv_stats_reviews', $aProduct['reviews_cnt']),
+            'reviews_url' => $aProduct['reviews_url'],
+            'votes' => _t('_adm_str_txt_pv_stats_votes', $aProduct['votes_cnt']),
+            'views' => _t('_adm_str_txt_pv_stats_views', $aProduct['views_cnt']),
+            'created' => bx_time_js($aProduct['created']),
+            'updated' => bx_time_js($aProduct['updated']),
+            'description' => $aProduct['description'],
+            'bx_if:show_screenshots' => array(
+                'condition' => $bScreenshots,
+                'content' => array(
+                    'width' => 222 * $iScreenshots - 20,
+                    'bx_repeat:screenshots' => $aScreenshots
+                )
+            ),
+            'bx_if:show_purchase' => array(
+            	'condition' => $bPurchaseSingle || $bPurchaseRecurring,
+            	'content' => array(
+            		'bx_if:show_purchase_single' => array(
+		                'condition' => $bPurchaseSingle && !$bInCart,
+		                'content' => array(
+		                    'js_object' => $sJsObject,
+		                    'id' => $aProduct['id'],
+		                    'vendor_id' => $aProduct['author_id'],
+		            		'price_single' => _t('_adm_str_txt_price_single', $aProduct['author_currency_sign'], $aProduct['price_single'])
+		                )
+		            ),
+		            'bx_if:show_checkout_single' => array(
+						'condition' => $bPurchaseSingle,
+						'content' => array(
+							'js_object' => $sJsObject,
+							'id' => $aProduct['id'],
+							'vendor_id' => $aProduct['author_id'],
+							'bx_if:show_as_hidden' => array(
+								'condition' => !$bInCart,
+								'content' => array()
+							)
+						)
+					),
+					'bx_if:show_purchase_recurring' => array(
+						'condition' => $bPurchaseRecurring,
+						'content' => array(
+							'js_object' => $sJsObject,
+							'id' => $aProduct['id'],
+							'vendor_id' => $aProduct['author_id'],
+							'price_recurring' => _t('_adm_str_txt_price_recurring', $aProduct['author_currency_sign'], $aProduct['price_recurring'], _t('_adm_str_txt_per_' . $aProduct['duration_recurring']))
+						)
+					)
+            	)
+            ),
+            'bx_if:show_download' => array(
+                'condition' => $bDownload && !$bDownloaded,
+                'content' => array(
+                    'js_object' => $sJsObject,
+                    'file_id' => $aProduct['file_id'],
+                )
+            ),
+            'bx_if:show_download_disabled' => array(
+				'condition' => $bDownload && $bDownloaded,
+				'content' => array()
+			),
+        ));
+
+        return array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => '', 'popup' => PopupBox('bx-std-str-popup-product', $aProduct['title'], $sContent, true), 'screenshots' => $iScreenshots);
+    }
+
+    protected function getFile($iFileId)
+    {
+        $mixedResult = $this->loadFile($iFileId);
+        if($mixedResult === true)
+        	return array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => _t('_adm_str_msg_download_successfully'));
+
+        if(is_string($mixedResult))
+			return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+
+		if(is_array($mixedResult))
+			return array_merge(array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_str_err_download_failed')), $mixedResult);
+    }
+
+	protected function getUpdate($sModuleName, $bAutoUpdate = false)
+    {
+        $mixedResult = $this->loadUpdate($sModuleName, $bAutoUpdate);
+        if($mixedResult === true)
+        	return array('code' => BX_DOL_STUDIO_IU_RC_SUCCESS, 'message' => _t('_adm_str_msg_download' . ($bAutoUpdate ? '_and_install' : '') . '_successfully'));
+
+		if(is_string($mixedResult))
+			return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+
+		if(is_array($mixedResult))
+        	return array_merge(array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_str_err_download_failed')), $mixedResult);
+    }
+
+    protected function displayProducts($mixedItems, $aParams = array())
+    {
+        $bShowRating = false;
+
+        if(!is_array($mixedItems))
+            return MsgBox($mixedItems);
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aDownloaded = $this->getDownloadedModules();
+        $bShoppingCart = isset($aParams['is_shopping_cart']) && $aParams['is_shopping_cart'];
+
+        $sResult = '';
+        foreach($mixedItems as $aItem) {
+            $bFree = (int)$aItem['is_free'] == 1;
+            $bPurchased = (int)$aItem['is_purchased'] == 1;
+
+            $bPurchaseSingle = !$bShoppingCart && !$bFree && !$bPurchased && (float)$aItem['price_single'] != 0;
+            $bInCart = $bPurchaseSingle && BxDolStudioCart::getInstance()->exists($aItem['author_id'], $aItem['id']);
+
+            $bPurchaseRecurring = !$bShoppingCart && !$bFree && !$bPurchased && (float)$aItem['price_recurring'] != 0;
+
+            $bDownloadable = (int)$aItem['is_file'] == 1;
+            $bDownloaded = in_array($aItem['name'], $aDownloaded);
+            $bDownload = !$bShoppingCart && ($bFree || $bPurchased) && $bDownloadable;
+            $bQueued = $this->oDb->isQueued('download', $aItem['name']);
+
+            $sPrice = !$bFree ? _t('_adm_str_txt_price_single', $aItem['author_currency_sign'], $aItem['price_single']) : _t('_adm_str_txt_price_free');
+            $sDiscount = !$bFree && !empty($aItem['discount_single']) ? _t('_adm_str_txt_discount_csign', $aItem['author_currency_sign'], $aItem['discount_single']['price']) : '';
+
+            $sIcon = !empty($aItem['thumbnail']['small']) ? $aItem['thumbnail']['small'] : BxDolStudioUtils::getIconDefault(BX_DOL_MODULE_TYPE_MODULE);
+            $bIcon = strpos($sIcon, '.') !== false;
+
+            $sImage = '';
+            if(!empty($aItem['cover']['medium']))
+                $sImage = $aItem['cover']['medium'];
+            else if(!empty($aItem['cover']['large']))
+                $sImage = $aItem['cover']['large'];
+            else if(!empty($aItem['cover']['big']))
+                $sImage = $aItem['cover']['big'];
+            $bImage = !empty($sImage) && strpos($sImage, '.') !== false;
+
+            $aTmplVarsRate = array();
+            $bTmplVarsRate = $bShowRating && !$bShoppingCart;
+            if($bTmplVarsRate)
+                $aTmplVarsRate = array(
+                    'rate' => $this->getVoteStars($aItem)
+                );
+
+            $aTmplVarsVotes = array();
+            $bTmplVarsVotes = $bShowRating && !$bShoppingCart && (int)$aItem['votes_cnt'] > 0;
+            if($bTmplVarsVotes)
+                $aTmplVarsVotes = array(
+                    'count' => (int)$aItem['votes_cnt']
+                );
+
+            $sResult .= $oTemplate->parseHtmlByName('str_product_v2.html', array(
+                'js_object' => $sJsObject,
+                'id' => $aItem['id'],
+            	'name' => $aItem['name'],
+                'url' => $aItem['url'],
+            	'description_plain_attr' => bx_html_attribute($aItem['description_plain']),
+            	'bx_if:no_icon' => array (
+	                'condition' => !$bIcon,
+	                'content' => array(
+            			'icon' => $sIcon
+            		),
+	            ),
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array(
+	                	'icon_url' => $sIcon,
+	            	),
+	            ),
+                'bx_if:no_image' => array (
+	                'condition' => !$bImage,
+	                'content' => array(
+            			'description_plain' => $aItem['description_plain'],
+	                	'strecher' => mb_strlen($aItem['description_plain']) > 240 ? '' : str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', round((240 - mb_strlen($aItem['description_plain'])) / 6))
+            		),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => $bImage,
+	                'content' => array(
+	                	'image_url' => $sImage,
+	            		'strecher' => str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', 40)
+	            	),
+	            ),
+                'title' => $aItem['title'],
+                'bx_if:show_vendor' => array(
+                    'condition' => !$bShoppingCart,
+                    'content' => array(
+                        'vendor_name' => $aItem['author_name'],
+	            		'vendor_url' => $aItem['author_url'],
+	            		'bx_if:show_version' => array(
+	            			'condition' => !empty($aItem['file_version']),
+	            			'content' => array(
+	            				'version' => $aItem['file_version'],
+	            			)
+	            		),
+                    )
+                ),
+                'bx_if:show_count_price' => array(
+                    'condition' => $bShoppingCart,
+                    'content' => array(
+                        'count' => isset($aParams['counts'][$aItem['id']]) ? (int)$aParams['counts'][$aItem['id']] : 1,
+                        'price_single' => $sPrice,
+                        'discount_single' => $sDiscount,
+                    )
+                ),
+                'bx_if:show_rate' => array(
+                    'condition' => $bTmplVarsRate,
+                    'content' => $aTmplVarsRate
+                ),
+                'bx_if:show_votes' =>  array(
+                    'condition' => $bTmplVarsVotes,
+                    'content' => $aTmplVarsVotes
+                ),
+                'bx_if:show_purchase_single' => array(
+                    'condition' => $bPurchaseSingle && !$bInCart,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id'],
+                		'price_single' => _t('_adm_str_txt_price_single', $aItem['author_currency_sign'], $aItem['price_single'])
+                    )
+                ),
+                'bx_if:show_checkout_single' => array(
+                    'condition' => $bPurchaseSingle,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id'],
+                		'bx_if:show_as_hidden' => array(
+                			'condition' => !$bInCart,
+                			'content' => array()
+                		)
+                    )
+                ),
+                'bx_if:show_purchase_recurring' => array(
+                	'condition' => $bPurchaseRecurring,
+                	'content' => array(
+                		'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id'],
+                		'price_recurring' => _t('_adm_str_txt_price_recurring', $aItem['author_currency_sign'], $aItem['price_recurring'], _t('_adm_str_txt_per_' . $aItem['duration_recurring'] . '_short'))
+                	)
+                ),
+                'bx_if:show_download' => array(
+                    'condition' => $bDownload && !$bQueued && !$bDownloaded,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'file_id' => $aItem['file_id']
+                    )
+                ),
+                'bx_if:show_download_disabled' => array(
+					'condition' => $bDownload && !$bQueued && $bDownloaded,
+					'content' => array()
+				),
+				'bx_if:show_queued_disabled' => array(
+					'condition' => $bDownload && $bQueued && !$bDownloaded,
+					'content' => array()
+				),
+                'bx_if:show_delete' => array(
+                    'condition' => $bShoppingCart,
+                    'content' => array(
+                        'js_object' => $sJsObject,
+                        'id' => $aItem['id'],
+                        'vendor_id' => $aItem['author_id']
+                    )
+                )
+            ));
+        }
+
+        return $sResult;
+    }
+
+    protected function displayUpdates($mixedItems, $aParams = array())
+    {
+        if(!is_array($mixedItems))
+            return MsgBox($mixedItems);
+
+		if(empty($mixedItems))
+			return MsgBox(_t('_Empty'));
+
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sResult = '';
+        foreach($mixedItems as $aItem) {
+            $bDownloadable = (int)$aItem['is_file'] == 1;
+
+            $sIcon = !empty($aItem['thumbnail']['big']) ? $aItem['thumbnail']['big'] : BxDolStudioUtils::getIconDefault(BX_DOL_MODULE_TYPE_MODULE);
+			$bIcon = strpos($sIcon, '.') !== false;
+
+            $sImage = !empty($aItem['cover']['big']) ? $aItem['cover']['big'] : '';
+			$bImage = strpos($sImage, '.') !== false;
+
+            $sResult .= $oTemplate->parseHtmlByName('str_update_v2.html', array(
+                'js_object' => $sJsObject,
+                'id' => $aItem['id'],
+            	'name' => $aItem['name'],
+                'url' => $aItem['url'],
+            	'bx_if:no_icon' => array (
+	                'condition' => !$bIcon,
+	                'content' => array(
+            			'icon' => $sIcon
+            		),
+	            ),
+                'bx_if:icon' => array (
+	                'condition' => $bIcon,
+	                'content' => array(
+	                	'icon_url' => $sIcon,
+	            	),
+	            ),
+            	'bx_if:no_image' => array (
+	                'condition' => !$bImage,
+	                'content' => array(
+	                	'note' => $aItem['description_plain'],
+	                	'strecher' => mb_strlen($aItem['description_plain']) > 240 ? '' : str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', round((240 - mb_strlen($aItem['description_plain'])) / 6))
+            		),
+	            ),
+                'bx_if:image' => array (
+	                'condition' => $bImage,
+	                'content' => array(
+						'image_url' => $sImage,
+						'strecher' => str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', 40)
+	            	),
+	            ),
+                'title' => $aItem['title'],
+                'vendor' => $aItem['author_name'],
+                'versions' => _t('_adm_str_txt_update_from_to', $aItem['file_version'], $aItem['file_version_to']),
+                'bx_if:show_download' => array(
+                    'condition' => $bDownloadable,
+                    'content' => array(
+	            		'caption' => _t($this->bAuthAccessUpdates ? '_adm_btn_download_submit' : '_adm_btn_install_submit'),
+	            		'on_click' => $sJsObject . "." . ($this->bAuthAccessUpdates ? "getFile(" . $aItem['file_id'] . ", this)" : "getUpdateAndInstall('" . $aItem['name'] . "', this)")
+                    )
+                )
+            ));
+        }
+
+        return $sResult;
+    }
+
+    protected function displayCategories($aCategories)
+    {
+        foreach($aCategories['bx_repeat:cats'] as $iKey => $aItem)
+            $aCategories['bx_repeat:cats'][$iKey]['url'] = $this->getBaseUrl(array('page' => 'category', 'value' => $aItem['value'], 'title' => urlencode($aItem['name'])));
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('str_lbl_categories.html', $aCategories);
+    }
+
+    protected function displayTags($aTags)
+    {
+        foreach($aTags['bx_repeat:units'] as $iKey => $aItem)
+            $aTags['bx_repeat:units'][$iKey]['href'] = $this->getBaseUrl(array('page' => 'tag', 'value' => $aItem['keyword']));
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('str_lbl_tags.html', $aTags);
+    }
+
+    private function getDownloadedModules($bNamesOnly = true)
+    {
+		$aModules = BxDolStudioInstallerUtils::getInstance()->getModules(false);
+
+        return $bNamesOnly ? array_keys($aModules) : $aModules;
+    }
+
+	private function getVoteStars($aItem)
+    {
+        $aTmplVarsStars = array();
+        for($i = (int)$aItem['rate_min']; $i <= (int)$aItem['rate_max']; $i++)
+            $aTmplVarsStars[] = array('value' => $i);
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('str_vote_stars.html', array(
+        	'id' => $aItem['id'],
+        	'value' => (float)$aItem['rate'],
+            'bx_repeat:stars' => $aTmplVarsStars,
+            'bx_repeat:slider' => $aTmplVarsStars,
+        ));
+    }
+}
+
+/** @} */

+ 778 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/css/common.css

@@ -0,0 +1,778 @@
+
+* {
+    -webkit-font-smoothing: antialiased;
+    -webkit-text-size-adjust: 100%;
+    -moz-text-size-adjust: 100%;
+    -ms-text-size-adjust: 100%;
+    text-size-adjust: 100%;
+}
+
+a {
+    text-decoration:none;
+    outline:none;
+    color:#3e8685; /* 26.13.13: prev val #387877; */
+}
+
+a:hover {
+    text-decoration:none;
+}
+
+a > img {
+    border:none;
+}
+
+html {
+    height:100%;
+}
+body {
+	min-height: 100%;
+    min-height: 100vh;
+    padding: 0;
+    margin: 0;
+}
+
+blockquote {
+    /* NOTE: this style was copied to editor.css */
+    border-left: 2px solid #ccc;
+    padding-left: 10px;
+}
+
+.bx-main {
+	position: relative;
+	display: -webkit-flex;
+	display: flex;
+	-webkit-flex-direction: column;
+	flex-direction: column;
+
+	min-height: 100%;
+	min-height: 100vh;
+}
+
+.bx-clear {
+    clear:both;
+    float:none;
+    display:block;
+    width: 0px;
+    height: 0px;
+    font-size:1px;
+}
+
+.bx-clearfix:before,
+.bx-clearfix:after {
+    content:"";
+    display:table;
+}
+.bx-clearfix:after {
+    clear:both;
+}
+
+.bx-anchor-invisible,
+.bx-anchor {
+    padding-top:60px;
+    margin-top:-60px;
+    -webkit-background-clip:content-box;
+    background-clip:content-box;
+}
+.bx-anchor-invisible {
+    display:block;
+    height:0px;
+    width:0px;
+    overflow:hidden;
+}
+
+.bx-bullet:before {
+    content: "\00A0\00b7\00A0";
+
+    color: #999999;
+}
+.bx-bullet-narrow:before {
+    content: "\00b7";
+
+    color: #999999;
+}
+
+.sys-icon-a {
+    width:1.5rem;
+    height:1.5rem;
+}
+
+.bx-search-site {
+    width:20rem;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+div.mce-fullscreen {
+    z-index: 1200;
+}
+
+.bx-acl-err-msg {
+    padding: 0 10%;
+}
+
+/* --------------- design boxes */
+
+.bx-db-container {
+    position:relative;
+}
+.bx-db-header {
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+
+    -webkit-box-align: stretch;
+    -webkit-align-items: stretch;
+    -ms-flex-align: stretch;
+    align-items: stretch;
+
+    -webkit-flex-wrap: nowrap;
+    -ms-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+}
+.bx-db-title {
+    -webkit-flex: 1 1 auto;
+    -ms-flex: 1 1 auto;
+    flex: 1 1 auto;
+
+    line-height: 40px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.bx-db-menu {
+	-webkit-flex: 0 2 auto;
+    -ms-flex: 0 2 auto;
+    flex: 0 2 auto;
+    
+    line-height:40px;
+}
+
+.bx-db-menu-tab {
+    float: left;
+    margin-left:5px;
+}
+
+.bx-db-menu-tab-active {
+    text-decoration:underline;
+}
+
+.bx-db-menu-tab-btn a {
+    text-decoration:none;
+}
+
+.bx-db-menu .sys-icon-a {
+    vertical-align:middle;
+}
+
+/* --------------- popup boxes */
+
+html.bx-popup-lock, body.bx-popup-lock {
+    overflow: hidden;
+}
+
+body > .bx-popup-wrapper {
+    max-height:100%;
+    overflow-x:hidden;
+    overflow-y:auto;
+    /* -webkit-overflow-scrolling:touch; */
+}
+
+.bx-popup-transitions {
+    -webkit-transition: opacity 0.2s ease-in-out, -webkit-transform 0.2s ease-in-out;
+    transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
+}
+
+.bx-popup-inactive {
+    -webkit-opacity: 0;
+    opacity: 0;
+
+    -webkit-transform: scale(0.75);
+    transform: scale(0.75);
+}
+
+.bx-popup-active {
+    max-height:100%;
+    overflow-y:auto;
+
+    -webkit-opacity: 1;
+    opacity: 1;
+
+    -webkit-transform: scale(1);
+    transform: scale(1);
+
+    -webkit-transition: opacity 0.2s ease-in-out, -webkit-transform 0.2s ease-in-out;
+    transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
+}
+
+.bx-popup-width {
+	width: 500px;
+}
+
+.bx-popup-border {
+	border:1px solid #383d66;
+}
+
+.bx-popup-color-bg {
+    background-color:#fff;
+}
+
+.bx-popup {    
+    overflow:hidden;
+
+	-moz-box-shadow:0px 0px 0px 1px rgba(255, 255, 255, 0.15), 0px 10px 25px 0px rgba(33, 33, 33, 0.5);
+	-webkit-box-shadow:0px 0px 0px 1px rgba(255, 255, 255, 0.15), 0px 10px 25px 0px rgba(33, 33, 33, 0.5);
+    box-shadow:0px 0px 0px 1px rgba(255, 255, 255, 0.15), 0px 10px 25px 0px rgba(33, 33, 33, 0.5);
+}
+
+.bx-popup-header {
+    position: relative;
+}
+.bx-popup-header-color-bg {
+	background-color: #3b8686;
+}
+
+.bx-popup-header .bx-db-menu-tab-btn {
+    padding-top:16px;
+}
+
+.bx-popup-header h2 {
+    margin:0;
+    text-align:center;
+    color:#FFFFFF;
+}
+
+.bx-popup-box-pointer {
+    display:none;
+    position:relative;
+    top:10px;
+    width:13px;
+    height:7px;
+    overflow:hidden;
+}
+
+.bx-popup-box-pointer > i {
+    display:block;
+    position:absolute;
+    top:-5px;
+    width:0px;
+    height:0px;
+    border-width:6px;
+    border-style:solid;
+    border-top-color:transparent;
+    border-right-color:transparent;
+    border-left-color:transparent;
+}
+
+.bx-popup-image-container {
+    position: relative;
+}
+
+.bx-popup-image-btn {
+    position: absolute;
+    right: 0;
+    top: 0;
+}
+
+.bx-popup-image-wrapper img {
+    display: block;
+}
+
+.bx-popup-element-close:hover,
+.bx-popup-element-close {
+    position:absolute;
+    top:0;
+    right:0;
+    text-decoration:none;
+    line-height:1.8rem;
+}
+
+.bx-popup-slide-wrapper {
+	width:100%;
+
+	background-color: rgba(255, 255, 255, 0.9);
+}
+
+.bx-popup-slide-wrapper li {
+    text-align:right;
+}
+
+.bx-popup-image-wrapper .bx-popup-close-wrapper,
+.bx-popup-image-wrapper .bx-popup-element-close {
+    display:none;
+}
+
+#bx-popup-alert,
+#bx-popup-confirm,
+#bx-popup-prompt {
+	max-width: 500px;
+	max-width: 31.25rem;
+}
+
+.popup_alert_actions .bx-btn,
+.popup_confirm_actions .bx-btn,
+.popup_prompt_actions .bx-btn {
+    float: none;
+    display: inline-block;
+}
+
+/* --------------- full screen popup */
+.bx-popup-full-screen.bx-popup-trans-wrapper,
+.bx-popup-full-screen.bx-popup-box-wrapper,
+.bx-popup-full-screen .bx-popup-trans,
+.bx-popup-full-screen .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-popup-full-screen .bx-popup-trans > div,
+.bx-popup-full-screen .bx-popup-box > div {
+    border-radius:0;
+    -moz-border-radius:0;
+    -webkit-border-radius:0;
+}
+
+.bx-popup-full-screen.bx-popup-trans-wrapper .bx-popup-border,
+.bx-popup-full-screen.bx-popup-box-wrapper .bx-popup-border {
+    border:none;
+}
+
+.bx-popup-full-screen.bx-popup-trans-wrapper .bx-popup,
+.bx-popup-full-screen.bx-popup-box-wrapper .bx-popup {
+    -moz-box-shadow:none;
+    -webkit-box-shadow:none;
+    box-shadow:none;
+}
+
+.bx-popup-full-screen .bx-popup-width {
+    width:100%;
+}
+
+.bx-popup-full-screen .bx-popup .bx-popup-close-wrapper {
+	display:block !important;
+}
+
+/* --------------- notification boxes */
+.bx-popup-inline-holder {
+	padding-top: 70px;
+   	padding-top: 4.375rem;
+}
+.bx-popup-inline {
+	width: 100%;
+}
+.bx-popup-inline .bx-popup {
+	position: relative;
+
+	width: 100%;
+	height: 68px;
+    height: 4.25rem;
+
+    border-width: 1px 0px 1px 0px;
+	border-color: #000000;
+	border-radius: 0px;
+
+	background-color: rgba(85, 85, 85, .9);
+
+	color: #ffffff;
+}
+.bx-popup-inline .bx-popup .bx-popup-content {
+	position: relative;
+	display: flex;
+
+	height: 100%;
+
+	padding: 0px 60px;
+	padding: 0px 3.75rem;
+
+	align-items: center; 
+}
+.bx-popup-inline .bx-popup .bx-popup-content > * {
+	max-height: 100%; 
+}
+.bx-popup-inline .bx-popup .bx-popup-actions {
+	position: absolute;
+	display: flex;
+
+	height: 100%;
+	top: 0px;
+
+	align-items: center;
+}
+.bx-popup-inline .bx-popup .bx-pa-left {
+	left: 0px;
+}
+.bx-popup-inline .bx-popup .bx-pa-right {
+	right: 0px;
+}
+.bx-popup-inline .bx-popup .bx-popup-actions a {
+	text-decoration: none;
+	font-size: 28px;
+	font-size: 1.75rem;
+	color: #9c9c9c;
+}
+
+/* --------------- chain notifications */
+div.bx-popup-chain {
+    position: relative;
+    max-width: 22.5rem;
+}
+div.bx-popup-chain-cnt {
+    position: relative;
+}
+div.bx-popup-chain-item {
+    position: relative;
+    width: 100%;
+    overflow: hidden;
+}
+div.bx-popup-chain-item-actions {
+    position: absolute;
+    top: 0px;
+}
+div.bx-popup-cia-left {
+    left: 0px;
+}
+div.bx-popup-cia-right {
+    right: 0px;
+}
+div.bx-popup-chain-item-actions a {
+    line-height: initial;
+    text-decoration: none;
+}
+div.bx-popup-chain-item-actions .sys-icon {
+	display: inline-block;
+	width: 1.5rem;
+	height: 1.5rem;
+	line-height: 1.5rem;
+	text-align: center;
+}
+div.bx-popup-chain-item .bx-loading-ajax {
+    height: auto;
+}
+
+/* --------------- message box */
+.bx-msg-box-container {
+    padding:0px 5px;
+    text-align:center;
+}
+
+.bx-msg-box {
+    display:inline-block; 
+    margin-right:auto;
+    margin-left:auto;
+
+    text-align:center;
+    font-size:1rem;
+    padding:10px;
+    text-transform:uppercase;
+}
+
+/* --------------- loading indicator */
+
+.bx-loading-ajax {
+    position:absolute;
+    width:100px;
+    height:100px;
+    margin-left:auto;
+    margin-right:auto;
+
+    background-color: transparent;
+
+	border-radius:10px;
+	-moz-border-radius:10px;
+	-webkit-border-radius:10px;
+
+    text-align:center;
+    color:#CCCCCC;
+}
+.bx-loading-ajax.static {
+	position: static;
+}
+.bx-loading-ajax svg {
+	width: 50px;
+	height: 50px;
+	padding: 25px;  
+}
+
+.bx-loading-ajax-btn {
+	position: relative;
+    display: inline-block;
+
+    vertical-align: top;
+
+    margin-left: 5px;
+}
+
+@-moz-document url-prefix() {
+	button.bx-btn .bx-loading-ajax-btn {
+		top: -1px;
+	}
+}
+
+button.bx-btn:focus,
+button.bx-btn:focus .bx-loading-ajax-btn {
+    outline: 0;	
+}
+
+
+/* --------------- SVG loading indicator */
+svg .outer, 
+svg .inner{
+	-webkit-transform-origin: 50% 50%;
+   	-moz-transform-origin: 50% 50%;
+    transform-origin: 50% 50%;
+}
+svg .outer{
+	-webkit-animation: clockwise 5s infinite linear;
+	-moz-animation: clockwise 5s infinite linear;
+    animation: clockwise 5s infinite linear;
+    
+
+}
+svg .inner{
+	-webkit-animation: counter-clockwise 2s infinite linear;
+	-moz-animation: counter-clockwise 2s infinite linear;
+    animation: counter-clockwise 2s infinite linear;
+    
+}
+svg .colored .inner .p1 {
+	fill:#2AB191;
+}
+svg .colored .inner .p2,
+svg .colored .outer .p1 {
+	fill:#4990E2;
+}
+svg .colored .outer .p2 {
+	fill:#EE6666;
+}
+svg .gray .inner .p1,
+svg .gray .inner .p2,
+svg .gray .outer .p1,
+svg .gray .outer .p2 {
+	fill:#4A4A4A;
+}
+
+@-webkit-keyframes counter-clockwise {
+    0% { -webkit-transform: rotate(0deg); }
+    100% { -webkit-transform: rotate(360deg); }
+}
+@-webkit-keyframes clockwise {
+    0% { -webkit-transform: rotate(360deg); }
+    100% { -webkit-transform: rotate(0deg); }
+}	
+@-moz-keyframes counter-clockwise {
+    0% { -moz-transform: rotate(0deg); }
+    100% { -moz-transform: rotate(360deg); }
+}
+@-moz-keyframes clockwise {
+    0% { -moz-transform: rotate(360deg); }
+    100% { -moz-transform: rotate(0deg); }
+}
+
+@keyframes counter-clockwise {
+    0% { transform: rotate(0deg); }
+    100% { transform: rotate(360deg); }
+}
+@keyframes clockwise {
+    0% { transform: rotate(360deg); }
+    100% { transform: rotate(0deg); }
+}
+	
+/* --------------- button */
+
+.bx-btn {
+    position:relative;
+	display:block;	
+    float:left;
+
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    box-sizing: border-box;
+
+    height:36px;
+    height:2.2rem;
+    margin:0;
+    padding:0px 12px 0px 12px;
+
+    background-color: #6caa8a;
+
+    border:none;
+	border-radius:3px;
+	-moz-border-radius:3px;
+	-webkit-border-radius:3px;	
+
+    -webkit-transition: background-color 0.2s ease-in-out;
+    -moz-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;
+
+    line-height:36px;
+    line-height:2.2rem;
+    text-decoration:none;
+    cursor: pointer;
+    font-size:1rem;
+    color:#fff;
+}
+.bx-btn-height {
+	height: 36px;
+    height: 2.2rem;
+    line-height: 36px;
+    line-height: 2.2rem;
+}
+.bx-btn-border {
+	border: none;
+}
+.bx-btn:active,
+.bx-btn:hover {	
+    background-color: #79bd9a;
+    text-decoration:none;
+}
+button.bx-btn,
+input.bx-btn {
+	padding-top:0px;
+	padding-bottom:0px;    
+}
+
+.bx-btn img {
+    width:1.2em;
+    height:1.2em;
+    vertical-align:middle;
+    background-size:1.2em 1.2em;
+    background-position:center center;
+}
+.bx-btn u {
+    margin-left: 10px;
+    text-decoration: none;
+}
+.bx-btn u:first-child {
+	margin-left: 0px;
+}
+.bx-btn .sys-icon {
+    font-size:1.2em;
+}
+.bx-btn .sys-icon-bigger {
+    font-size:1.4em;
+}
+
+.bx-btn.bx-btn-small {
+    height:24px; 
+    height:1.6rem; 
+    padding-left:0px 8px 0px 8px;
+
+    line-height:24px;
+    line-height:1.5rem;
+    font-size:0.9rem;
+}
+
+.bx-btn-small-height {
+	height: 24px; 
+    height: 1.6rem;
+    line-height: 24px;
+    line-height: 1.5rem;
+}
+
+.bx-btn-small-border {
+	border: none;
+}
+
+.bx-btn.bx-btn-primary {
+    background-color: #357877;
+}
+.bx-btn.bx-btn-primary:hover,
+.bx-btn.bx-btn-primary:active { 
+    background-color: #3a8686;
+}
+
+.bx-btn.bx-btn-disabled:active,
+.bx-btn.bx-btn-disabled:hover,
+.bx-btn.bx-btn-disabled,
+.bx-btn.bx-btn-small.bx-btn-disabled:active,
+.bx-btn.bx-btn-small.bx-btn-disabled:hover,
+.bx-btn.bx-btn-small.bx-btn-disabled,
+.bx-btn.bx-btn-primary.bx-btn-disabled:active,
+.bx-btn.bx-btn-primary.bx-btn-disabled:hover,
+.bx-btn.bx-btn-primary.bx-btn-disabled {
+    background-color: #ddd;
+}
+
+.bx-btn.bx-btn-symbol {
+    font-family: "Times New Roman", Georgia, Serif;
+    font-size:1.3rem;
+    line-height:32px;
+}
+.bx-btn.bx-btn-symbol:hover {
+    text-decoration:none;
+}
+.bx-btn.bx-btn-symbol-small {
+    font-size:1rem;
+}
+
+.bx-btn-group .bx-btn {
+    margin-left:0px;
+    margin-right:0px;
+}
+.bx-btn-group .bx-btn:not(:first-child):after {
+    content:"";
+    position:absolute;
+    display:block;
+    top:0px;
+    left:-0.05em;
+    width:0.1em;
+    height:100%;
+    background-color:rgba(255,255,255,0.1);
+}
+.bx-btn-group .bx-btn + .bx-btn:not(:last-child) {
+	border-radius:0px;
+	-moz-border-radius:0px;
+	-webkit-border-radius:0px;    
+}
+.bx-btn-group .bx-btn:first-child {
+    -webkit-border-top-right-radius: 0px;
+    -webkit-border-bottom-right-radius: 0px;
+    -moz-border-radius-topright: 0px;
+    -moz-border-radius-bottomright: 0px;
+    border-top-right-radius: 0px;
+    border-bottom-right-radius: 0px;
+}
+.bx-btn-group .bx-btn:last-child {
+    -webkit-border-top-left-radius: 0px;
+    -webkit-border-bottom-left-radius: 0px;
+    -moz-border-radius-topleft: 0px;
+    -moz-border-radius-bottomleft: 0px;
+    border-top-left-radius: 0px;
+    border-bottom-left-radius: 0px;
+}
+
+/* --------------- button at the end of cut string */
+
+.bx-str-limit {
+    height:10px;
+    width:14px;    
+    margin-left:3px;
+
+	border-radius:3px;
+	-moz-border-radius:3px;
+	-webkit-border-radius:3px;
+    border-collapse:separate;
+
+	-moz-box-shadow:0px 0px 1px rgba(92, 92, 92, 0.8);
+	-webkit-box-shadow:0px 0px 1px rgba(92, 92, 92, 0.8);
+    box-shadow:0px 0px 1px rgba(92, 92, 92, 0.8);
+
+    cursor:pointer;
+}
+
+/* --------------- categories */
+
+.bx-category-list-item {
+	line-height: 24px;
+	line-height: 1.5rem;
+}

+ 644 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/scripts/BxBaseFunctions.php

@@ -0,0 +1,644 @@
+<?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;
+
+        $oStorage = BxDolTranscoder::getObjectInstance('sys_custom_images');
+        $sFileUrl = $oStorage->getFileUrl($iFileId);
+        if (!$sFileUrl) 
+            return false;
+
+		$iLogoWidth = (int)$oDesigns->getSiteLogoWidth();
+        if($iLogoWidth > 0)
+            $sFileUrl = bx_append_url_params($sFileUrl, array('x' => $iLogoWidth));
+
+		$iLogoHeight = (int)$oDesigns->getSiteLogoHeight();
+        if($iLogoHeight > 0)
+            $sFileUrl = bx_append_url_params($sFileUrl, array('y' => $iLogoHeight));
+
+        return $sFileUrl;
+    }
+
+    /**
+     * Get logo HTML.
+     * @return string
+     */
+    function getMainLogo()
+    {
+		$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) . '" />';
+        }
+
+        return '<a class="bx-def-font-contrasted" href="' . BX_DOL_URL_ROOT . '" title="' . bx_html_attribute($sAlt, BX_ESCAPE_STR_QUOTE) . '">' . $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 $sUrlWebM .webm video
+     * @param $aAttrs custom attributes, defaults are: controls="" preload="none" autobuffer=""
+     * @param $sStyles custom styles, defaults are: width:100%; height:auto;
+     */
+    function videoPlayer ($sUrlPoster, $sUrlMP4, $sUrlWebM = '', $aAttrs = false, $sStyles = 'width:100%; height:auto;')
+    {
+        $aAttrsDefaults = array(
+            'controls' => '',
+            'controlsList' => 'nodownload',
+            'preload' => 'none',
+            'autobuffer' => '', 
+        );
+        $aAttrs = array_merge($aAttrsDefaults, is_array($aAttrs) ? $aAttrs : array());
+        $sAttrs = bx_convert_array2attrs($aAttrs, '', $sStyles);
+
+        return '<video ' . $sAttrs . ' poster="' . $sUrlPoster . '">
+                    ' . ($sUrlWebM ? '<source type="video/webm; codecs="vp8, vorbis" src="' . $sUrlWebM . '" />' : '') . '
+                    ' . ($sUrlMP4  ? '<source type="video/mp4" src="' . $sUrlMP4 . '" />' : '') . '
+                </video>';
+    }
+
+    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 '';
+
+        $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'),
+            'iProfileId' => $iProfileId,
+            'sAppId' => $sAppId,
+            'sShortName' => $sShortName,
+            'sSafariWebId' => $sSafariWebId,
+            'sSubfolder' => $sSubfolder,
+            'sNotificationUrl' => BX_DOL_URL_ROOT,
+            'sTxtNotificationRequest' => bx_js_string(_t('_sys_push_notification_request', getParam('site_title'))),
+            'sTxtNotificationRequestYes' => bx_js_string(_t('_sys_push_notification_request_yes')),
+            'sTxtNotificationRequestNo' => bx_js_string(_t('_sys_push_notification_request_no')),
+        )) . ");";
+
+        return $this->_oTemplate->_wrapInTagJsCode($sContent);
+    }
+
+    protected function getInjFooterMenuLoading() 
+    {
+        return $this->_oTemplate->parsePageByName('menu_loading.html', array());  
+    }
+
+    protected function getInjFooterPopupLoading() 
+    {
+        return $this->transBox('bx-popup-loading', $this->_oTemplate->parsePageByName('popup_loading.html', array()), true);  
+    }
+
+    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 '';
+    }
+}
+
+/** @} */

+ 246 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/scripts/BxBaseMenu.php

@@ -0,0 +1,246 @@
+<?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
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseMenu extends BxDolMenu
+{
+    protected $_oTemplate;
+    protected $_aOptionalParams = array('target' => '', 'onclick' => '');
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+    }
+
+    /**
+     * Get menu code.
+     * @return string
+     */
+    public function getCode ()
+    {
+        $sMenuTitle = isset($this->_aObject['title']) ? _t($this->_aObject['title']) : 'Menu-' . rand(0, PHP_INT_MAX);
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginMenu($sMenuTitle);
+
+        $s = '';
+        $aVars = $this->_getTemplateVars ();
+        if (!empty($aVars['bx_repeat:menu_items'])) {
+            $this->_addJsCss();
+            $s = $this->_oTemplate->parseHtmlByName($this->_aObject['template'], $aVars);
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endMenu($sMenuTitle);
+
+        return $s;
+    }
+    
+    public function getCodeItem ($sName)
+    {
+        if(empty($sName))
+            return '';
+
+        $sCode = $this->_oTemplate->getHtml(str_replace('.html', '_item.html', $this->_aObject['template']));
+        if(empty($sCode))
+            return '';
+
+        $mixedTmplVars = $this->getMenuItem($sName);
+        if($mixedTmplVars === false)
+            return '';
+
+        return $this->_oTemplate->parseHtmlByContent($sCode, $mixedTmplVars);
+    }
+
+    /**
+     * Get template variables array
+     * @return array
+     */
+    protected function _getTemplateVars ()
+    {
+        return array (
+            'object' => $this->_sObject,
+            'bx_repeat:menu_items' => $this->getMenuItems (),
+        );
+    }
+
+    /**
+     * Get menu items array, which is ready to pass to menu template. 
+     * @return array or false
+     */
+    public function getMenuItems ()
+    {
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
+
+        $aItems = array();
+        foreach ($this->_aObject['menu_items'] as $aItem) {
+            $aItem = $this->_getMenuItem ($aItem);
+            if($aItem !== false)
+                $aItems[] = $aItem;
+        }
+
+        return $aItems;
+    }
+    
+    /**
+     * Get menu item array, which is ready to pass to whole menu or 
+     * single menu item template. May return false if single menu item 
+     * is requested but cannot be shown by circumstances.
+     * @return array or false
+     */
+    public function getMenuItem ($sName)
+    {
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->getMenuItemsRaw ();
+
+        if(!empty($sName))
+            return $this->_getMenuItem($this->_aObject['menu_items'][$sName]);
+    }
+
+    /**
+     * Get menu items array, this is just a wrapper for DB function for make it easier to override.
+     * It is used in @see BxBaseMenu::getMenuItems
+     * @return array
+     */
+    protected function getMenuItemsRaw ()
+    {
+        return $this->_oQuery->getMenuItems();
+    }
+
+	protected function _getMenuItem ($a)
+	{
+        $aHiddenOn = array(
+			pow(2, BX_DB_HIDDEN_PHONE - 1) => 'bx-def-media-phone-hide',
+			pow(2, BX_DB_HIDDEN_TABLET - 1) => 'bx-def-media-tablet-hide',
+			pow(2, BX_DB_HIDDEN_DESKTOP - 1) => 'bx-def-media-desktop-hide'
+		);
+        
+		if (isset($a['active']) && !$a['active'])
+			return false;
+
+		if (isset($a['visible_for_levels']) && !$this->_isVisible($a))
+        	return false;
+
+		$a['title'] = _t($a['title']);
+		$a['bx_if:title'] = array(
+			'condition' => !empty($a['title']),
+			'content' => array(
+				'title' => $a['title']
+			)
+		);
+
+        $this->removeMarker('addon');
+
+		$a = $this->_replaceMarkers($a);
+
+		$mixedAddon = $this->_getMenuAddon($a);
+		$this->addMarkers(array('addon' => $mixedAddon));
+
+		$a = $this->_replaceMarkers($a);
+
+		list ($sIcon, $sIconUrl, $sIconA) = $this->_getMenuIcon($a);
+
+		$a['class_add'] = $this->_isSelected($a) ? 'bx-menu-tab-active' : '';
+        
+        $sHiddenOnCssClasses = '';
+        if(!empty($a['hidden_on']))
+            foreach($aHiddenOn as $iHiddenOn => $sClass)
+                if((int)$a['hidden_on'] & $iHiddenOn)
+                    $sHiddenOnCssClasses .= ' ' . $sClass;
+        $a['class_add'] .=  $sHiddenOnCssClasses;
+		$a['link'] = isset($a['link']) ? $this->_oPermalinks->permalink($a['link']) : 'javascript:void(0);';
+		$a['title_attr'] = bx_html_attribute(strip_tags($a['title']));
+		$a['bx_if:image'] = array (
+			'condition' => (bool)$sIconUrl,
+			'content' => array('icon_url' => $sIconUrl),
+		);
+		$a['bx_if:icon'] = array (
+        	'condition' => (bool)$sIcon,
+            'content' => array('icon' => $sIcon),
+		);
+		$a['bx_if:icon-a'] = array (
+        	'condition' => (bool)$sIconA,
+            'content' => array('icon-a' => $sIconA),
+		);
+		$a['bx_if:title'] = array (
+			'condition' => (bool)$a['title'],
+			'content' => array(
+				'title' => $a['title'],
+		        'title_attr' => $a['title_attr']
+		    ),
+		);
+		$a['bx_if:addon'] = array (
+			'condition' => (bool)$mixedAddon,
+			'content' => $this->_getTmplVarsAddon($mixedAddon, $a)
+		);
+
+		foreach ($this->_aOptionalParams as $sName => $sDefaultValue)
+        	if (!isset($a[$sName]))
+            	$a[$sName] = $sDefaultValue;
+
+		return $a;
+	}
+
+    protected function _getMenuIcon ($a)
+    {
+        $sIcon = false;
+        $sIconA = false;
+        $sIconUrl = false;
+        if (!empty($a['icon'])) {
+            if ((int)$a['icon'] > 0 ) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+                $sIconUrl = $oStorage ? $oStorage->getFileUrlById((int)$a['icon']) : false;
+            } else {
+                if (false === strpos($a['icon'], '.')) { 
+                    if (0 === strncmp($a['icon'], 'a:', 2))
+                        $sIconA = substr($a['icon'], 2); // animated icon
+                    else
+                        $sIcon = $a['icon']; // font icons
+                } else {
+                    $sIconUrl = $this->_oTemplate->getIconUrl($a['icon']);
+                }
+            }
+        }
+        return array ($sIcon, $sIconUrl, $sIconA);
+    }
+
+    protected function _getMenuAddon ($aMenuItem)
+    {
+        if (empty($aMenuItem['addon']))
+            return '';
+
+        return BxDolService::callSerialized($aMenuItem['addon'], $this->_aMarkers);
+    }
+
+    /**
+     * Add css/js files which are needed for menu display and functionality.
+     */
+    protected function _addJsCss()
+    {
+        $this->_oTemplate->addCss('menu.css');
+    }
+
+	protected function _getTmplVarsAddon($mixedAddon, $aMenuItem) 
+	{
+	    return array(
+	    	'addon' => $mixedAddon,
+	        'addonf' => ''
+	    );
+	}
+}
+
+/** @} */

+ 487 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/files/template/scripts/BxBasePage.php

@@ -0,0 +1,487 @@
+<?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
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . 'design.inc.php');
+
+/**
+ * Page representation.
+ * @see BxDolPage
+ */
+class BxBasePage extends BxDolPage
+{
+    protected $_oTemplate;
+
+    protected $_sStorage; //--- Storage object for page's images like custom cover, HTML block attachments, etc.
+    protected $_oPageCacheObject = null;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+		$this->_sStorage = 'sys_images';
+    }
+
+    /**
+     * Get page code with automatic caching, adding necessary css/js files and system template vars.
+     * @return string.
+     */
+    public function getCode ()
+    {
+        if (bx_get('dynamic') && ($iBlockId = (int)bx_get('pageBlock'))) {
+
+            if (!$this->_isVisiblePage($this->_aObject)) {
+                header('HTTP/1.0 403 Forbidden');
+                exit;
+            }
+
+            bx_alert('system', 'page_output_block', 0, false, array(
+                'page_name' => $this->_sObject,
+                'page_object' => $this,
+                'page_query' => $this->_oQuery,
+                'block_id' => (int)$iBlockId,
+            ));
+
+            header( 'Content-type:text/html;charset=utf-8' );
+            echo $this->_getBlockOnlyCode($iBlockId);
+            exit;
+        }
+
+        if (!$this->_isVisiblePage($this->_aObject))
+            return $this->_getPageAccessDeniedMsg ();
+
+        $this->_addJsCss();
+
+        $this->_addSysTemplateVars();
+
+        $this->_selectMenu();
+
+        $this->_setSubmenu(array());
+
+        if (!getParam('sys_page_cache_enable') || !$this->_aObject['cache_lifetime']) {
+            $sPageCode = $this->_getPageCode();
+        }
+        else {
+            $oCache = $this->_getPageCacheObject();
+            $sKey = $this->_getPageCacheKey();
+
+            $mixedRet = $oCache->getData($sKey, $this->_aObject['cache_lifetime']);
+
+            if ($mixedRet !== null) {
+                $sPageCode = $mixedRet;
+            } else {
+                $sPageCode = $this->_getPageCode();
+                $oCache->setData($sKey, $sPageCode, $this->_aObject['cache_lifetime']);
+            }
+        }
+        
+        bx_alert('system', 'page_output', 0, false, array(
+            'page_name' => $this->_sObject,
+            'page_object' => $this,
+            'page_query' => $this->_oQuery,
+            'page_code' => &$sPageCode,
+        ));
+
+        return $sPageCode;
+    }
+
+    /**
+     * Is page cover enabled.
+     * @return string
+     */
+    public function isPageCover()
+    {
+    	return (int)$this->_aObject['cover'] == 1;
+    }
+
+    public function setPageCover($bCover = true)
+    {
+        $this->_aObject['cover'] = (bool)$bCover;
+    }
+
+	public function getPageCoverImage($bTranscoder = true)
+    {
+    	$iId = (int)$this->_aObject['cover_image'];
+    	if(empty($iId)) {
+    		$iId = (int)getParam('sys_site_cover_common');
+    		if(empty($iId))
+    			return array();
+    	}
+
+    	$aResult = array(
+    		'id' => $iId
+    	);
+    	if($bTranscoder)
+    		$aResult['transcoder'] = BX_DOL_TRANSCODER_OBJ_COVER;
+    	else 
+    		$aResult['object'] = $this->_sStorage;
+
+    	return $aResult;
+    }
+
+    public function getPageCoverParams()
+    {
+    	$aParams = BxDolMenu::getObjectInstance('sys_site_submenu')->getPageCoverParams();
+    	if(!empty($aParams) && is_array($aParams))
+    		return $aParams;
+
+    	return array (
+            'title' => $this->_getPageTitle(),
+            'actions' => '',
+            'bx_if:image' => array (
+                'condition' => false,
+                'content' => array(),
+            ),
+            'bx_if:icon' => array (
+                'condition' => false,
+                'content' => array(),
+            ),
+        );
+    }
+
+    /**
+     * Get block title.
+     * @return string
+     */
+    public function getBlockTitle ($aBlock)
+    {
+        return $this->_replaceMarkers(_t($aBlock['title']));
+    }
+
+    /**
+     * Get page code only.
+     * @return string
+     */
+    protected function _getPageCode ()
+    {
+    	$aHiddenOn = array(
+			pow(2, BX_DB_HIDDEN_PHONE - 1) => 'bx-def-media-phone-hide',
+			pow(2, BX_DB_HIDDEN_TABLET - 1) => 'bx-def-media-tablet-hide',
+			pow(2, BX_DB_HIDDEN_DESKTOP - 1) => 'bx-def-media-desktop-hide'
+		);
+
+        $aVars = array (
+            'page_id' => 'bx-page-' . $this->_aObject['uri'],
+        );
+        $aBlocks = $this->_oQuery->getPageBlocks();
+        foreach ($aBlocks as $sKey => $aCell) {
+            $sCell = '';
+            foreach ($aCell as $aBlock) {
+                $sContentWithBox = $this->_getBlockCode($aBlock);
+
+            	$sHiddenOn = '';
+		    	if(!empty($aBlock['hidden_on']))
+		    		foreach($aHiddenOn as $iHiddenOn => $sClass)
+		    			if((int)$aBlock['hidden_on'] & $iHiddenOn)
+		    				$sHiddenOn .= ' ' . $sClass;
+    	
+    	
+                if ($sContentWithBox)
+                    $sCell .= '<div class="bx-page-block-container bx-def-padding-sec-topbottom' . $sHiddenOn . '" id="bx-page-block-' . $aBlock['id'] . '">' . $sContentWithBox . '</div>';
+            }
+            $aVars[$sKey] = $sCell;
+        }
+
+        return $this->_oTemplate->parseHtmlByName($this->_aObject['template'], $aVars);
+    }
+
+    /**
+     * Get one block code only.
+     * @return string
+     */
+    protected function _getBlockOnlyCode ($iBlockId)
+    {
+        $aBlock = $this->_oQuery->getPageBlock((int)$iBlockId);
+        return $this->_getBlockCode($aBlock);
+    }
+
+    /**
+     * Get block code.
+     * @return string
+     */
+    protected function _getBlockCode($aBlock)
+    {
+        $sContentWithBox = '';
+        $oFunctions = $this->_oTemplate->getTemplateFunctions();
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginPageBlock(_t($aBlock['title']), $aBlock['id']);
+
+        if ($this->_isVisibleBlock($aBlock)) {
+
+            $sFunc = '_getBlock' . ucfirst($aBlock['type']);
+            $mixedContent = $this->$sFunc($aBlock);
+
+            $sTitle = $this->getBlockTitle($aBlock);
+
+            if (is_array($mixedContent) && !empty($mixedContent['content'])) {
+				$sContentWithBox = $oFunctions->designBoxContent(
+                	isset($mixedContent['title']) ? $mixedContent['title'] : $sTitle,
+                    $mixedContent['content'],
+                    isset($mixedContent['designbox_id']) ? $mixedContent['designbox_id'] : $aBlock['designbox_id'],
+                    isset($mixedContent['menu']) ? $mixedContent['menu'] : false
+				);
+            } 
+            elseif (is_string($mixedContent) && !empty($mixedContent)) {                    
+                $sContentWithBox = $oFunctions->designBoxContent($sTitle, $mixedContent, $aBlock['designbox_id']);
+            }
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endPageBlock($aBlock['id'], $sContentWithBox ? false : true, false );
+
+        return $sContentWithBox;
+    }
+
+    /**
+     * Add necessary js and css files.
+     */
+    protected function _addJsCss()
+    {
+        $this->_oTemplate->addCss('page_layouts.css');
+    }
+
+    /**
+     * Set system template variables, like page title, meta description, meta keywords and meta robots.
+     */
+    protected function _addSysTemplateVars ()
+    {
+        $sPageTitle = $this->_getPageTitle();
+        if ($sPageTitle)
+            BxDolTemplate::getInstance()->setPageHeader ($sPageTitle);
+
+        $sMetaDesc = $this->_getPageMetaDesc();
+        if ($sMetaDesc)
+            BxDolTemplate::getInstance()->setPageDescription ($sMetaDesc);
+
+        $sMetaKeywords = $this->_getPageMetaKeywords();
+        if ($sMetaKeywords)
+            BxDolTemplate::getInstance()->addPageKeywords ($sMetaKeywords);
+
+        $sMetaRobots = $this->_getPageMetaRobots();
+        if ($sMetaRobots)
+            BxDolTemplate::getInstance()->setPageMetaRobots ($sMetaRobots);
+    }
+
+    /**
+     * Select menu from page properties.
+     */
+    protected function _selectMenu ()
+    {
+        BxDolMenu::setSelectedGlobal ($this->_aObject['module'], $this->_aObject['uri']);
+    }
+
+    /**
+     * Set page submenu if it's specified
+     */
+    protected function _setSubmenu ($aParams)
+    {
+        if(empty($this->_aObject['submenu']))
+            return;
+
+        BxDolMenu::getObjectInstance('sys_site_submenu')->setObjectSubmenu($this->_aObject['submenu'], $aParams);
+    }
+
+    /**
+     * Get content for 'raw' block type.
+     * @return string
+     */
+    protected function _getBlockRaw ($aBlock)
+    {
+        return '<div class="bx-page-raw-container">' . $this->_replaceMarkers(BxDolTemplate::getInstance()->parseHtmlByContent($aBlock['content'], array())) . '</div>';
+    }
+
+    /**
+     * Get content for 'html' block type.
+     * @return string
+     */
+    protected function _getBlockHtml ($aBlock)
+    {
+        return '<div class="bx-page-html-container">' . $this->_replaceMarkers($aBlock['content']) . '</div>';
+    }
+
+    /**
+     * Get content for 'lang' block type.
+     * @return string
+     */
+    protected function _getBlockLang ($aBlock)
+    {
+        return '<div class="bx-page-lang-container">' . $this->_replaceMarkers(_t(trim($aBlock['content']))) . '</div>';
+    }
+
+    /**
+     * Get content for 'image' block type.
+     * @return string
+     */
+    protected function _getBlockImage ($aBlock)
+    {
+        if (empty($aBlock['content']))
+            return false;
+
+        list($iFileId, $sAlign ) = explode('#', $aBlock['content']);
+        $iFileId = (int)$iFileId;
+        if (!$iFileId)
+            return false;
+
+        $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+        if (!$oStorage)
+            return false;
+
+        $sUrl = $oStorage->getFileUrlById($iFileId);
+        if (!$sUrl)
+            return false;
+
+        $sStyleAdd = '';
+        if ('center' == $sAlign || 'left' == $sAlign || 'right' == $sAlign)
+            $sStyleAdd = 'style="text-align:' . $sAlign . '"';
+
+        return '<div class="bx-page-image-container" ' . $sStyleAdd . '><img src="' . $sUrl . '" /></div>';
+    }
+
+    /**
+     * Get content for 'rss' block type.
+     * @return string
+     */
+    protected function _getBlockRss ($aBlock)
+    {
+        if (empty($aBlock['content']))
+            return false;
+
+        list( $sUrl, $iNum ) = explode('#', $aBlock['content']);
+        $iNum = (int)$iNum;
+
+        return BxDolRss::getObjectInstance('sys_page_block')->getHolder($aBlock['id'], $iNum);
+    }
+
+    /**
+     * Get content for 'menu' block type.
+     * @return string
+     */
+    protected function _getBlockMenu ($aBlock)
+    {
+        $oMenu = BxTemplMenu::getObjectInstance($aBlock['content']);
+        return $oMenu ? $oMenu->getCode () : '';
+    }
+
+    /**
+     * Get content for 'service' block type.
+     * @return string
+     */
+    protected function _getBlockService ($aBlock)
+    {
+        return BxDolService::callSerialized($aBlock['content'], $this->_aMarkers);
+    }
+
+    /**
+     * Get page title.
+     * @return string
+     */
+    protected function _getPageTitle()
+    {
+        return $this->_replaceMarkers(_t($this->_aObject['title']));
+    }
+
+    /**
+     * Get page meta description.
+     * @return string
+     */
+    protected function _getPageMetaDesc()
+    {
+        return $this->_replaceMarkers(_t($this->_aObject['meta_description']));
+    }
+
+    /**
+     * Get page meta keywords.
+     * @return string
+     */
+    protected function _getPageMetaKeywords()
+    {
+        return $this->_replaceMarkers(_t($this->_aObject['meta_keywords']));
+    }
+
+    /**
+     * Get page meta robots.
+     * @return string
+     */
+    protected function _getPageMetaRobots()
+    {
+        return _t($this->_aObject['meta_robots']);
+    }
+
+    /**
+     * Get access denied message HTML.
+     * @return string
+     */
+    protected function _getPageAccessDeniedMsg ()
+    {
+        return MsgBox(_t('_Access denied'));
+    }
+
+    /**
+     * Get page cache object.
+     * @return cache object instance
+     */
+    protected function _getPageCacheObject ()
+    {
+        if ($this->_oPageCacheObject != null) {
+            return $this->_oPageCacheObject;
+        } else {
+            $sEngine = getParam('sys_page_cache_engine');
+            $this->_oPageCacheObject = bx_instance ('BxDolCache' . $sEngine);
+            if (!$this->_oPageCacheObject->isAvailable())
+                $this->_oPageCacheObject = bx_instance ('BxDolCacheFile');
+            return $this->_oPageCacheObject;
+        }
+    }
+
+    /**
+     * Get page cache key.
+     * @param $isPrefixOnly return key prefix only.
+     * @return string
+     */
+    protected function _getPageCacheKey ($isPrefixOnly = false)
+    {
+        $s = 'page_' . $this->_aObject['object'] . '_';
+        if ($isPrefixOnly)
+            return $s;
+        $s .= $this->_getPageCacheParams ();
+        $s .= bx_site_hash() . '.php';
+        return $s;
+    }
+
+    /**
+     * Additional cache key. In the case of dynamic page.
+     * For example - profile page, where each profile has own page.
+     * @return string
+     */
+    protected function _getPageCacheParams ()
+    {
+        return '';
+    }
+
+    /**
+     * Clean page cache.
+     * @param $isDelAllWithPagePrefix delete cache by prefix, it can be used for dynamic pages, like profile view, where for each profile separate cache is generated.
+     * @return string
+     */
+    protected function cleanCache ($isDelAllWithPagePrefix = false)
+    {
+        $oCache = $this->_getPageCacheObject ();
+        $sKey = $this->_getPageCacheKey($isDelAllWithPagePrefix);
+
+        if ($isDelAllWithPagePrefix)
+            return $oCache->removeAllByPrefix($sKey);
+        else
+            return $oCache->delData($sKey);
+    }
+}
+
+/** @} */

+ 3 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/script.php

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

+ 6 - 0
upgrade/files/9.0.0.RC12-9.0.0.RC13/sql.sql

@@ -0,0 +1,6 @@
+
+
+-- Last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '9.0.0-RC13' WHERE (`version` = '9.0.0.RC12' OR `version` = '9.0.0-RC12') AND `name` = 'system';
+