Просмотр исходного кода

Merge branch 'master' of https://github.com/unaio/una

AntonLV 7 лет назад
Родитель
Сommit
6cf9ff69fd
45 измененных файлов с 18654 добавлено и 2 удалено
  1. 1 1
      inc/version.inc.php
  2. 1 1
      install/index.php
  3. 9 0
      upgrade/files/10.0.0-10.1.0.B1/check.php
  4. 378 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolAccountQuery.php
  5. 420 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolAclQuery.php
  6. 138 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCacheFile.php
  7. 1689 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCmts.php
  8. 533 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCmtsQuery.php
  9. 176 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCmtsReviews.php
  10. 317 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolConnectionQuery.php
  11. 353 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolProfileQuery.php
  12. 89 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolRelationQuery.php
  13. 752 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/js/classes/BxDolCmts.js
  14. 72 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/js/classes/BxDolPush.js
  15. 483 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/js/classes/BxDolUploader.js
  16. 1101 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/js/functions.js
  17. 12 0
      upgrade/files/10.0.0-10.1.0.B1/files/inc/version.inc.php
  18. 140 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/connect/classes/BxBaseModConnectDb.php
  19. 596 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php
  20. 1879 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/general/classes/BxBaseModGeneralModule.php
  21. 228 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php
  22. 1580 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/profile/classes/BxBaseModProfileModule.php
  23. 118 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/classes/BxBaseModTextFormsEntryHelper.php
  24. 778 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/classes/BxBaseModTextTemplate.php
  25. 275 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/template/css/polls.css
  26. 6 0
      upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/template/poll_items_showcase.html
  27. 8 0
      upgrade/files/10.0.0-10.1.0.B1/files/plugins_public/onesignal/OneSignalSDKUpdaterWorker.js.php
  28. 9 0
      upgrade/files/10.0.0-10.1.0.B1/files/plugins_public/onesignal/OneSignalSDKWorker.js.php
  29. 195 0
      upgrade/files/10.0.0-10.1.0.B1/files/studio/classes/BxDolStudioMenuTop.php
  30. 24 0
      upgrade/files/10.0.0-10.1.0.B1/files/studio/template/css/menu.css
  31. 107 0
      upgrade/files/10.0.0-10.1.0.B1/files/studio/template/scripts/BxBaseStudioLauncher.php
  32. 333 0
      upgrade/files/10.0.0-10.1.0.B1/files/studio/template/scripts/BxBaseStudioPage.php
  33. 2 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/comment_quote.html
  34. 430 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/css/cmts.css
  35. 703 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/css/default.less
  36. 486 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/css/general.css
  37. 865 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/css/menu.css
  38. 21 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/menu_account_popup.html
  39. 17 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/menu_vertical.html
  40. 1144 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseCmts.php
  41. 169 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseCmtsMenuActions.php
  42. 1769 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseFormView.php
  43. 216 0
      upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseServiceLogin.php
  44. 18 0
      upgrade/files/10.0.0-10.1.0.B1/script.php
  45. 14 0
      upgrade/files/10.0.0-10.1.0.B1/sql.sql

+ 1 - 1
inc/version.inc.php

@@ -7,6 +7,6 @@
  * @{
  */
 
-define('BX_DOL_VERSION', '10.0.0-DEV5'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+define('BX_DOL_VERSION', '10.1.0-B1'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
 
 /** @} */

+ 1 - 1
install/index.php

@@ -10,7 +10,7 @@
 define ('BX_DOL_INSTALL',  true);
 define ('BX_SKIP_INSTALL_CHECK', true);
 
-define ('BX_DOL_VER', '10.0.0-DEV5');
+define ('BX_DOL_VER', '10.1.0-B1');
 
 define ('BX_INSTALL_DEFAULT_LANGUAGE', 'en');
 

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

+ 378 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolAccountQuery.php

@@ -0,0 +1,378 @@
+<?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
+ * @{
+ */
+
+/**
+ * All queries related to account
+ */
+class BxDolAccountQuery extends BxDolDb implements iBxDolSingleton
+{
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolAccountQuery();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public function getOperators ()
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_accounts` WHERE `role`&" . BX_DOL_ROLE_ADMIN);
+        return $this->getColumn($sSql);
+    }
+    
+    /**
+     * Get account by specified field name and value.
+     * It is for internal usage only.
+     * Use other funtions to get account info, like getInfoById, etc.
+     * @param  string $sField database field name
+     * @param  mixed  $sValue database field value
+     * @return array  with porfile info
+     */
+    protected function _getDataByField ($sField, $sValue)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_accounts` WHERE `$sField` = ? LIMIT 1", $sValue);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * Get account info by id
+     * @param  int   $iID account id
+     * @return array with account info
+     */
+    public function getInfoById( $iID )
+    {
+        return $this->_getDataByField('id', (int)$iID);
+    }
+
+    /**
+     * get account id by emial
+     */
+    public function getIdByEmail($sEmail)
+    {
+        return (int)$this->_getFieldByField('id', 'email', $sEmail);
+    }
+    
+    /**
+     * get account id by phone
+     */
+    public function getIdByPhone($sPhone)
+    {
+        return (int)$this->_getFieldByField('id', 'phone', $sPhone);
+    }
+
+    /**
+     * get account id by id
+     */
+    public function getIdById($iId, $bClearCache = false)
+    {
+        if (!$iId)
+            return false;
+
+        $sKey = 'BxDolAccountQuery::getIdById' . $iId;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+
+        $sSql = $this->prepare("SELECT `id` FROM `sys_accounts` WHERE `id` = ? LIMIT 1", $iId);
+        $bResult = $this->fromMemory($sKey, 'getOne', $sSql);
+        if (!$bResult)
+            $this->cleanMemory($sKey);
+        return $bResult;
+    }
+
+    /**
+     * get first studio operator id
+     */
+    public function getStudioOperatorId()
+    {
+        return (int)$this->_getFieldByField('id', 'role', 3);
+    }
+
+    /**
+     * Get account email by id
+     * @param  string  $s search account by this id
+     * @return account email
+     */
+    public function getEmail($iID)
+    {
+        return $this->_getFieldByField('email', 'id', (int)$iID);
+    }
+
+    /**
+     * Get account password by id
+     * @param  string  $s search account by this id
+     * @return account password
+     */
+    public function getPassword($iID)
+    {
+        return $this->_getFieldByField('password', 'id', (int)$iID);
+    }
+
+	/**
+     * Is account online by id
+     * @param  int $iId account id 
+     * @return account online status
+     */
+    public function isOnline($iId)
+    {
+        $sSql = $this->prepare("SELECT 
+        		`ta`.`id` 
+        	FROM `sys_accounts` AS `ta` 
+        	INNER JOIN `sys_sessions` AS `ts` ON `ta`.`id`=`ts`.`user_id` 
+        	WHERE 
+        		`ta`.`id` = ? AND 
+        		`ts`.`date` > (UNIX_TIMESTAMP() - 60 * ?) 
+        	LIMIT 1", $iId, (int)getParam('sys_account_online_time'));
+        return (int)$this->getOne($sSql) > 0;
+    }
+
+    /**
+     * Update last logged in time
+     * @param $sPasswordHash - password hash
+     * @param $sSalt - pasword salt
+     * @param $iAccountId - account id to update password for
+     * @return number of affected rows
+     */
+    public function updatePassword($sPasswordHash, $sSalt, $iAccountId)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `password` = ?, `salt` = ? WHERE `id`= ?", $sPasswordHash, $sSalt, $iAccountId);
+        return $this->query($sQuery);
+    }
+
+    /**
+     * Update last logged in time
+     * @param  int    $iID account id
+     * @return number of affected rows
+     */
+    public function updateLoggedIn($iID)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `logged` = ?, `ip` = ?, `login_attempts` = 0 WHERE `id`= ?", time(), getVisitorIP(), $iID);
+        return $this->query($sQuery);
+    }
+    
+    /**
+     * Update login attempts counter
+     * @param  int    $iID account id
+     * @return number of affected rows
+     */
+    public function updateAttemptsCounter($iID)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `login_attempts` = `login_attempts` + 1 WHERE `id`= ?", $iID);
+        return $this->query($sQuery);
+    }
+    
+    /**
+     * Lock account
+     * @param  int    $iID account id
+     * @return number of affected rows
+     */
+    public function lockAccount($iID)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `locked` = 1 WHERE `id`= ?", $iID);
+        return $this->query($sQuery);
+    }
+    
+    /**
+     * Unlock account
+     * @param  int    $iID account id
+     * @return number of affected rows
+     */
+    public function unlockAccount($iID)
+    {
+        $sQuery = $this->prepare("UPDATE `sys_accounts` SET `locked` = 0, `login_attempts` = 0 WHERE `id`= ?", $iID);
+        return $this->query($sQuery);
+    }
+
+    /**
+     * Update language
+     * @param  int    $iID account id
+     * @return number of affected rows
+     */
+    public function updateLanguage($iID, $iLangId)
+    {
+        return $this->_updateField ($iID, 'lang_id', $iLangId);
+    }
+
+    /**
+     * Update current profile id associated with account
+     * @param  int    $iID        account id
+     * @param  int    $iProfileId set current profile id to this value
+     * @return number of affected rows
+     */
+    public function updateCurrentProfile($iID, $iProfileId)
+    {
+        if ($bResult = $this->_updateField ($iID, 'profile_id', $iProfileId))
+            $this->cleanMemory('BxDolProfileQuery::getCurrentProfileByAccount' . $iID);
+
+        return $bResult;
+    }
+
+    /**
+     * Update 'email_confirmed' field.
+     * @param  int    $isConfirmed - 0: mark email as unconfirmed, 1: as confirmed
+     * @param  int    $iID         - account id
+     * @return number of affected rows
+     */
+    public function updateEmailConfirmed($isConfirmed, $iID)
+    {
+        return $this->_updateField ($iID, 'email_confirmed', $isConfirmed ? 1 : 0);
+    }
+    
+    /**
+     * Update 'phone_confirmed' field.
+     * @param  int    $isConfirmed - 0: mark phone as unconfirmed, 1: as confirmed
+     * @param  int    $iID         - account id
+     * @return number of affected rows
+     */
+    public function updatePhoneConfirmed($isConfirmed, $iID)
+    {
+        return $this->_updateField ($iID, 'phone_confirmed', $isConfirmed ? 1 : 0);
+    }
+    
+    /**
+     * Update 'phone' field.
+     * @param  int    $sPhone - phone number
+     * @param  int    $iID         - account id
+     * @return number of affected rows
+     */
+    public function updatePhone($sPhone, $iID)
+    {
+        $sSql = $this->prepare("UPDATE `sys_accounts` SET `phone` = ?, `phone_confirmed` = 0 WHERE `id` = ? LIMIT 1", $sPhone, $iID);
+        return $this->query($sSql);
+    }
+
+    /**
+     * Get account field by specified field name and value.
+     * In most cases it is for internal usage only.
+     * Use other funtions to get account info, like getIdByEmail, etc.
+     * @param  string    $sFieldRequested database field name to return
+     * @param  string    $sFieldSearch    database field name to search for
+     * @param  mixed     $sValue          database field value
+     * @return specified account field value
+     */
+    protected function _getFieldByField ($sFieldRequested, $sFieldSearch, $sValue)
+    {
+        $sSql = $this->prepare("SELECT `$sFieldRequested` FROM `sys_accounts` WHERE `$sFieldSearch` = ? LIMIT 1", $sValue);
+        return $this->getOne($sSql);
+    }
+
+    /**
+     * Update some field by account id
+     * In most cases it is for internal usage only.
+     * Use other funtions to get account info, like updateLogged, etc.
+     * @param  string    $sFieldRequested database field name to return
+     * @param  string    $sFieldSearch    database field name to search for
+     * @param  mixed     $sValue          database field value
+     * @return specified account field value
+     */
+    protected function _updateField ($iId, $sFieldForUpdate, $sValue)
+    {
+        $sSql = $this->prepare("UPDATE `sys_accounts` SET `$sFieldForUpdate` = ? WHERE `id` = ? LIMIT 1", $sValue, $iId);
+        return $this->query($sSql);
+    }
+
+    /**
+     * Delete account info by id
+     * @param  int      $iID profile id
+     * @return affected rows
+     */
+    public function delete($iID)
+    {
+        $sSql = $this->prepare("DELETE FROM `sys_accounts` WHERE `id` = ? LIMIT 1", $iID);
+        if ($res = $this->query($sSql)) {
+            $this->getIdById($iID, true);
+        }
+        return $res;
+    }
+
+	/**
+     * Get account(s) by params
+     * @param  array   $aParams browse params
+     * @return array with account(s)
+     */
+    public function getAccounts($aParams)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sFieldsClause = "`ta`.*"; 
+    	$sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = "";
+
+    	switch($aParams['type']) {
+    		case 'by_join_date':
+    			$aMethod['params'][1] = array(
+    				'date' => $aParams['date']
+    			);
+
+    			$sWhereClause = " AND `ta`.`added` > :date AND `ta`.`added` < UNIX_TIMESTAMP()";
+    			break;
+                
+            case 'confirmed':
+    			$sWhereClause = " AND (`ta`.`email_confirmed` = 1 OR `ta`.`phone_confirmed` = 1)";
+    			break;
+                
+            case 'unconfirmed':
+    			$sWhereClause = " AND (`ta`.`email_confirmed` = 0 AND `ta`.`phone_confirmed` = 0)";
+    			break;
+    	}
+
+    	$sGroupClause = $sGroupClause ? "GROUP BY " . $sGroupClause : "";
+		$sOrderClause = $sOrderClause ? "ORDER BY " . $sOrderClause : "";
+
+		$aMethod['params'][0] = "SELECT
+				" . $sFieldsClause . "
+            FROM `sys_accounts` AS `ta`" . $sJoinClause . "
+            WHERE 1" . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    /**
+     * Search account profile by keyword
+     */
+    public function searchByTerm($sTerm, $iLimit)
+    {
+    	$aBindings = array(
+    		'system' => 'system',
+    		'status' => BX_PROFILE_STATUS_ACTIVE,
+    		'limit' => (int)$iLimit
+    	);
+
+        $sWhere = '';
+        $aFieldsQuickSearch = array('name', 'email');
+        foreach ($aFieldsQuickSearch as $sField) {
+        	$aBindings[$sField] = '%' . $sTerm . '%';
+
+            $sWhere .= " OR `c`.`$sField` LIKE :$sField ";
+        }
+
+        $sQuery = "SELECT `c`.`id` AS `content_id`, `p`.`account_id`, `p`.`id` AS `profile_id`, `p`.`status` AS `profile_status` FROM `sys_accounts` AS `c` INNER JOIN `sys_profiles` AS `p` ON (`p`.`content_id` = `c`.`id` AND `p`.`type` = :system) WHERE `p`.`status` = :status AND (0 $sWhere) ORDER BY `added` DESC LIMIT :limit";
+        return $this->getAll($sQuery, $aBindings);
+    }
+}
+
+/** @} */

+ 420 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolAclQuery.php

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

+ 138 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCacheFile.php

@@ -0,0 +1,138 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+class BxDolCacheFile extends BxDolCache
+{
+    protected $sPath;
+
+    /**
+     * constructor
+     */
+    function __construct()
+    {
+        parent::__construct();
+        $this->sPath = BX_DIRECTORY_PATH_CACHE;
+    }
+
+    /**
+     * Get all data from the cache file.
+     *
+     * @param  string $sKey - file name
+     * @param  int    $iTTL - time to live
+     * @return the    data is got from cache.
+     */
+    function getData($sKey, $iTTL = false)
+    {
+        if (!file_exists($this->sPath . $sKey))
+            return null;
+
+        if ($iTTL > 0 && $this->_removeFileIfTtlExpired ($this->sPath . $sKey, $iTTL))
+            return null;
+
+        include($this->sPath . $sKey);
+        if (!isset($mixedData))
+            return null;
+
+        return $mixedData;
+    }
+
+    /**
+     * Save all data in cache file.
+     *
+     * @param  string  $sKey      - file name
+     * @param  mixed   $mixedData - the data to be cached in the file
+     * @param  int     $iTTL      - time to live
+     * @return boolean result of operation.
+     */
+    function setData($sKey, $mixedData, $iTTL = false)
+    {
+        if(file_exists($this->sPath . $sKey) && !is_writable($this->sPath . $sKey))
+           return false;
+
+        if(!($rHandler = fopen($this->sPath . $sKey, 'w')))
+           return false;
+
+        fwrite($rHandler, '<?php $mixedData=' . var_export($mixedData, true) . '; ?>');
+        fclose($rHandler);
+        @chmod($this->sPath . $sKey, 0666);
+
+        if (function_exists('opcache_invalidate')) opcache_invalidate($this->sPath . $sKey);
+
+        return true;
+    }
+
+    /**
+     * Delete cache file.
+     *
+     * @param  string $sKey - file name
+     * @return result of the operation
+     */
+    function delData($sKey)
+    {
+        $sFile = $this->sPath . $sKey;
+        return !file_exists($sFile) || @unlink($sFile);
+    }
+
+    /**
+     * remove all data from cache by key prefix
+     * @return true on success
+     */
+    function removeAllByPrefix ($s)
+    {
+        if (!($rHandler = opendir($this->sPath)))
+            return false;
+
+        $l = strlen($s);
+        while (($sFile = readdir($rHandler)) !== false)
+            if (0 === strncmp($sFile, $s, $l))
+                @unlink ($this->sPath . $sFile);
+
+        closedir($rHandler);
+
+        return true;
+    }
+
+    /**
+     * get size of cached data by name prefix
+     */
+    function getSizeByPrefix ($s)
+    {
+        if (!($rHandler = opendir($this->sPath)))
+            return false;
+
+        $iSize = 0;
+        $l = strlen($s);
+        while (($sFile = readdir($rHandler)) !== false)
+            if (0 === strncmp($sFile, $s, $l))
+                $iSize += @filesize ($this->sPath . $sFile);
+
+        closedir($rHandler);
+
+        return $iSize;
+    }
+
+    /**
+     * remove file from dist if TTL expored
+     * @param  string $sFile - full path to filename
+     * @param  int    $iTTL  - time to live in seconds
+     * @return true   if TTL is expired and file is deleted or false otherwise
+     */
+    function _removeFileIfTtlExpired ($sFile, $iTTL)
+    {
+        $iTimeDiff = time() - filectime($sFile);
+        if ($iTimeDiff > $iTTL) {
+            @unlink ($sFile);
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
+
+/** @} */

+ 1689 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCmts.php

@@ -0,0 +1,1689 @@
+<?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
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+define('BX_DOL_CMT_TYPE_COMMENT', 'comment');
+define('BX_DOL_CMT_TYPE_REVIEW', 'review');
+
+define('BX_CMT_OLD_VOTES', 365*86400); ///< comment votes older than this number of seconds will be deleted automatically
+
+define('BX_CMT_ACTION_POST', 'post');
+define('BX_CMT_ACTION_EDIT', 'edit');
+
+define('BX_CMT_DISPLAY_FLAT', 'flat');
+define('BX_CMT_DISPLAY_THREADED', 'threaded');
+
+define('BX_CMT_BROWSE_HEAD', 'head');
+define('BX_CMT_BROWSE_TAIL', 'tail');
+define('BX_CMT_BROWSE_POPULAR', 'popular');
+define('BX_CMT_BROWSE_CONNECTION', 'connection');
+
+define('BX_CMT_ORDER_BY_DATE', 'date');
+define('BX_CMT_ORDER_BY_POPULAR', 'popular');
+
+define('BX_CMT_FILTER_ALL', 'all');
+define('BX_CMT_FILTER_OTHERS', 'others');
+define('BX_CMT_FILTER_FRIENDS', 'friends');
+define('BX_CMT_FILTER_SUBSCRIPTIONS', 'subscriptions');
+
+define('BX_CMT_ORDER_WAY_ASC', 'asc');
+define('BX_CMT_ORDER_WAY_DESC', 'desc');
+
+define('BX_CMT_PFP_TOP', 'top');
+define('BX_CMT_PFP_BOTTOM', 'bottom');
+
+define('BX_CMT_RATE_VALUE_PLUS', 1);
+define('BX_CMT_RATE_VALUE_MINUS', -1);
+
+define('BX_CMT_USAGE_BLOCK', 'block');
+define('BX_CMT_USAGE_INLINE', 'inline');
+define('BX_CMT_USAGE_DEFAULT', BX_CMT_USAGE_BLOCK);
+
+/**
+ * Comments for any content
+ *
+ * Related classes:
+ * - BxDolCmtsQuery - comments database queries
+ * - BxBaseCmts - comments base representation
+ * - BxTemplCmts - custom template representation
+ *
+ * AJAX comments for any content.
+ * Self moderated - users rate all comments, and if comment is
+ * below viewing treshold it is hidden by default.
+ *
+ * To add comments section to your module you need to add a record to 'sys_objects_cmts' table:
+ *
+ * - ID - autoincremented id for internal usage
+ * - ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ * - TableCmts - table name where comments are stored
+ * - Html - 0 - convert new lines to <br /> on saving, 1 - standard(default) visual editor, 2 - full visual editor, 3 - mini visual editor.
+ * - PerView - number of comments on a page
+ * - IsRatable - 0 or 1 allow to rate comments or not
+ * - ViewingThreshold - comment viewing treshost, if comment is below this number it is hidden by default
+ * - IsOn - is this comment object enabled
+ * - RootStylePrefix - toot comments style prefix, if you need root comments look different\
+ * - ObjectVote - Vote object name to process comments' votes. May be empty if Comment Vote is not needed.
+ * - TriggerTable - table to be updated upon each comment
+ * - TriggerFieldId - TriggerTable table field with unique record id of
+ * - TriggerFieldComments - TriggerTable table field with comments count, it will be updated automatically upon eaech comment
+ * - ClassName - your custom class name if you need to override default class, this class must have the same constructor arguments
+ * - ClassFile - file where your ClassName is stored.
+ *
+ * You can refer to BoonEx modules for sample record in this table.
+ *
+ *
+ *
+ * @section example Example of usage:
+ * After filling in the table you can show comments section in any place, using the following code:
+ *
+ * @code
+ * $o = new BxTemplCmts('value of ObjectName field', $iYourEntryId);
+ * if ($o->isEnabled())
+ *     echo $o->getCommentsFirst ();
+ * @endcode
+ *
+ * Please note that you never need to use BxDolCmts class directly, use BxTemplCmts instead.
+ * Also if you override comments class with your own then make it child of BxTemplCmts class.
+ *
+ *
+ *
+ * @section acl Memberships/ACL:
+ * - comments post
+ * - comments edit own
+ * - comments remove own
+ * - comments edit all
+ *
+ *
+ *
+ * @section alerts Alerts:
+ * Alerts type/unit - every module has own type/unit, it equals to ObjectName.
+ *
+ * The following alerts are rised
+ *
+ * - commentPost - comment was posted
+ *      - $iObjectId - entry id
+ *      - $iSenderId - author of comment
+ *      - $aExtra['comment_id'] - just added comment id
+ *
+ * - commentRemoved - comments was removed
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment deleter id
+ *      - $aExtra['comment_id'] - removed comment id
+ *
+ * - commentUpdated - comments was updated
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment deleter id
+ *      - $aExtra['comment_id'] - updated comment id
+ *
+ * - commentRated - comments was rated
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment rater id
+ *      - $aExtra['comment_id'] - rated comment id
+ *      - $aExtra['rate'] - comment rate 1 or -1
+ *
+ */
+class BxDolCmts extends BxDolFactory implements iBxDolReplaceable, iBxDolContentInfoService
+{
+    /**
+     * System tables which are UNITED for all 
+     * comment systems and cannot be overwritten.
+     */
+    public static $sTableSystems = 'sys_objects_cmts';
+    public static $sTableIds = 'sys_cmts_ids';
+
+    /**
+     * System tables which are used by default and
+     * can be overwritten in comment systems.
+     * @see BxDolCmts::getTableNameImages and BxDolCmts::getTableNameImages2Entries
+     */
+    protected $_sTableImages = 'sys_cmts_images';
+    protected $_sTableImages2Entries = 'sys_cmts_images2entries';
+
+    protected $_sType;
+    protected $_oQuery = null;
+    protected $_oTemplate = null;
+
+    protected $_bPostQuote;
+    protected $_bMinPostForm;
+    protected $_sFormObject;
+    protected $_sFormDisplayPost;
+    protected $_sFormDisplayEdit;
+
+    protected $_sConnObjFriends;
+    protected $_sConnObjSubscriptions;
+
+    protected $_sMenuObjManage;
+    protected $_sMenuObjActions;
+    protected $_sMenuObjMeta;
+
+    protected $_sMetatagsObj;
+
+    protected $_sViewUrl = '';
+    protected $_sBaseUrl = '';
+    protected $_sListAnchor = '';
+    protected $_sItemAnchor = '';
+
+    protected $_sSystem = 'profile'; ///< current comment system name
+    protected $_aSystem = array (); ///< current comments system array
+    protected $_iId = 0; ///< obect id to be commented
+
+    protected $_aT = array (); ///< an array of lang keys
+    protected $_aMarkers = array ();
+
+    protected $_sDisplayType = '';
+    protected $_sDpSessionKey = '';
+    protected $_iDpMaxLevel = 0;
+
+    protected $_sBrowseType = '';
+    protected $_bBrowseFilter = false;
+    protected $_sBrowseFilter = '';
+    protected $_sBpSessionKeyType = '';
+    protected $_sBpSessionKeyFilter = '';
+    protected $_aOrder = array();
+
+    protected $_sSnippetLenthLiveSearch = 50;
+
+    protected $_iRememberTime = 2592000;
+
+    /**
+     * Constructor
+     * $sSystem - comments system name
+     * $iId - obect id to be commented
+     */
+    protected function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct();
+
+        $this->_sType = BX_DOL_CMT_TYPE_COMMENT;
+
+        $this->_aSystems = $this->getSystems();
+        if(!isset($this->_aSystems[$sSystem]))
+            return;
+
+        $this->_sSystem = $sSystem;
+        $this->_aSystem = $this->_aSystems[$sSystem];
+
+        $this->_aSystem['is_browse_filter'] = (int)$this->_bBrowseFilter;
+
+        $this->_iDpMaxLevel = (int)$this->_aSystem['number_of_levels'];
+        $this->_sDisplayType = $this->_iDpMaxLevel == 0 ? BX_CMT_DISPLAY_FLAT : BX_CMT_DISPLAY_THREADED;
+        $this->_sDpSessionKey = 'bx_' . $this->_sSystem . '_dp_';
+
+        $this->_sBrowseType = $this->_aSystem['browse_type'];
+        $this->_sBrowseFilter = BX_CMT_FILTER_ALL;
+        $this->_sBpSessionKeyType = 'bx_' . $this->_sSystem . '_bpt_';
+        $this->_sBpSessionKeyFilter = 'bx_' . $this->_sSystem . '_bpf_';
+        $this->_aOrder = array(
+            'by' => BX_CMT_ORDER_BY_DATE,
+            'way' => BX_CMT_ORDER_WAY_ASC
+        );
+
+        list($mixedUserDp, $mixedUserBpType, $mixedUserBpFilter) = $this->_getUserChoice();
+        if(!empty($mixedUserDp))
+            $this->_sDisplayType = $mixedUserDp;
+        if(!empty($mixedUserBpType))
+            $this->_sBrowseType = $mixedUserBpType;
+        if(!empty($mixedUserBpFilter))
+            $this->_sBrowseFilter = $mixedUserBpFilter;
+
+        $this->_sViewUrl = BX_DOL_URL_ROOT . 'cmts.php';
+        $this->_sBaseUrl = BxDolPermalinks::getInstance()->permalink($this->_aSystem['base_url']);
+        if(get_mb_substr($this->_sBaseUrl, 0, 4) != 'http')
+            $this->_sBaseUrl = BX_DOL_URL_ROOT . $this->_sBaseUrl;
+        $this->_sListAnchor = "cmts-anchor-%s-%d";
+        $this->_sItemAnchor = "cmt-anchor-%s-%d-%d";
+
+        $this->_oQuery = new BxDolCmtsQuery($this);
+
+        $this->_bPostQuote = false;
+        $this->_bMinPostForm = true;
+        $this->_sFormObject = 'sys_comment';
+        $this->_sFormDisplayPost = 'sys_comment_post';
+        $this->_sFormDisplayEdit = 'sys_comment_edit';
+
+        $this->_sConnObjFriends = 'sys_profiles_friends';
+        $this->_sConnObjSubscriptions = 'sys_profiles_subscriptions';
+
+        $this->_sMenuObjManage = 'sys_cmts_item_manage';
+        $this->_sMenuObjActions = 'sys_cmts_item_actions';
+        $this->_sMenuObjMeta = 'sys_cmts_item_meta';
+
+        $this->_sMetatagsObj = 'sys_cmts';
+
+        $this->_aT = array(
+            'block_comments_title' => '_cmt_block_comments_title',
+            'txt_sample_single' => '_cmt_txt_sample_comment_single',
+            'txt_sample_vote_single' => '_cmt_txt_sample_vote_single',
+            'txt_sample_reaction_single' => '_cmt_txt_sample_reaction_single',
+            'txt_sample_score_up_single' => '_cmt_txt_sample_score_up_single',
+            'txt_sample_score_down_single' => '_cmt_txt_sample_score_down_single',
+            'txt_min_form_placeholder' => '_cmt_txt_min_form_placeholder'
+        );
+
+        if ($iInit)
+            $this->init($iId);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+    }
+
+    /**
+     * get comments object instanse
+     * @param $sSys comments object name
+     * @param $iId associated content id, where comments are postred in
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true, $oTemplate = false)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId]))
+            return $GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId];
+
+        $aSystems = self::getSystems();
+        if (!isset($aSystems[$sSys]))
+            return null;
+
+        $sClassName = 'BxTemplCmts';
+        if(!empty($aSystems[$sSys]['class_name'])) {
+            $sClassName = $aSystems[$sSys]['class_name'];
+            if(!empty($aSystems[$sSys]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSys]['class_file']);
+        }
+
+        $o = new $sClassName($sSys, $iId, $iInit, $oTemplate);
+        return ($GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId] = $o);
+    }
+
+    public static function &getSystems ()
+    {
+        $sKey = 'bx_dol_cache_memory_cmts_systems';
+
+        if (!isset($GLOBALS[$sKey])) {
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_cmts', 'getAllWithKey', '
+                SELECT
+                    `ID` as `system_id`,
+                    `Name` AS `name`,
+                    `Module` AS `module`,
+                    `Table` AS `table`,
+                    `CharsPostMin` AS `chars_post_min`,
+                    `CharsPostMax` AS `chars_post_max`,
+                    `CharsDisplayMax` AS `chars_display_max`,
+                    `Html` AS `html`,
+                    `PerView` AS `per_view`,
+                    `PerViewReplies` AS `per_view_replies`,
+                    `BrowseType` AS `browse_type`,
+                    `IsBrowseSwitch` AS `is_browse_switch`,
+                    `PostFormPosition` AS `post_form_position`,
+                    `NumberOfLevels` AS `number_of_levels`,
+                    `IsDisplaySwitch` AS `is_display_switch`,
+                    `IsRatable` AS `is_ratable`,
+                    `ViewingThreshold` AS `viewing_threshold`,
+                    `IsOn` AS `is_on`,
+                    `RootStylePrefix` AS `root_style_prefix`,
+                    `BaseUrl` AS `base_url`,
+                    `ObjectVote` AS `object_vote`,
+                    `ObjectReaction` AS `object_reaction`,
+                    `ObjectScore` AS `object_score`,
+                    `ObjectReport` AS `object_report`,
+                    `TriggerTable` AS `trigger_table`,
+                    `TriggerFieldId` AS `trigger_field_id`,
+                    `TriggerFieldAuthor` AS `trigger_field_author`,
+                    `TriggerFieldTitle` AS `trigger_field_title`,
+                    `TriggerFieldComments` AS `trigger_field_comments`,
+                    `ClassName` AS `class_name`,
+                    `ClassFile` AS `class_file`
+                FROM `' . self::$sTableSystems . '`', 'name');
+        }
+        return $GLOBALS[$sKey];
+    }
+
+    public static function getGlobalInfo ($iUniqueId)
+    {
+        return BxDolDb::getInstance()->getRow("SELECT `ti`.*, `ts`.`Name` AS `system_name` FROM `" . self::$sTableIds . "` AS `ti` LEFT JOIN `" . self::$sTableSystems . "` AS `ts` ON `ti`.`system_id`=`ts`.`ID` WHERE `ti`.`id`=:id LIMIT 1", array(
+            'id' => (int)$iUniqueId
+        ));
+    }
+
+    public function init ($iId)
+    {
+        if (empty($this->iId) && $iId)
+            $this->setId($iId);
+
+        $this->addMarkers(array(
+            'object_id' => $this->getId(),
+            'user_id' => $this->_getAuthorId()
+        ));
+    }
+
+    public function getId ()
+    {
+        return $this->_iId;
+    }
+
+    public function isEnabled ()
+    {
+        return isset($this->_aSystem['is_on']) && $this->_aSystem['is_on'];
+    }
+
+    public function getSystemId()
+    {
+        return $this->_aSystem['system_id'];
+    }
+
+    public function getSystemName()
+    {
+        return $this->_sSystem;
+    }
+
+    public function getSystemInfo()
+    {
+        return $this->_aSystem;
+    }
+
+    public function getStorageObjectName()
+    {
+    	return $this->_getFormObject()->getStorageObjectName();
+    }
+
+    public function getTranscoderPreviewName()
+    {
+    	return $this->_getFormObject()->getTranscoderPreviewName();
+    }
+
+    public function getTableNameImages()
+    {
+        return $this->_sTableImages;
+    }
+
+    public function getTableNameImages2Entries()
+    {
+        return $this->_sTableImages2Entries;
+    }
+
+    public function getLanguageKey($sIndex)
+    {
+        return isset($this->_aT[$sIndex]) ? $this->_aT[$sIndex] : '';
+    }
+
+    public function getMaxLevel()
+    {
+        return $this->_iDpMaxLevel;
+    }
+
+    public function getOrder ()
+    {
+        return $this->_sOrder;
+    }
+
+    public function getPerView ($iCmtParentId = 0)
+    {
+        return $iCmtParentId == 0 ? $this->_aSystem['per_view'] : $this->_aSystem['per_view_replies'];
+    }
+
+    public function getViewUrl($iCmtId)
+    {
+    	if(empty($this->_aSystem['trigger_field_title']))
+            return '';
+
+    	return bx_append_url_params($this->_sViewUrl, array(
+            'sys' => $this->_sSystem,
+            'id' => $this->_iId,
+            'cmt_id' => $iCmtId
+        ));
+    }
+
+    public function getBaseUrl()
+    {
+        return $this->_replaceMarkers($this->_sBaseUrl);
+    }
+
+    public function getListUrl()
+    {
+        $sBaseUrl = $this->getBaseUrl();
+        if(empty($sBaseUrl))
+            return '';
+
+        return $sBaseUrl . $this->getListAnchor(true);
+    }
+
+    public function getItemUrl($iItemId)
+    {
+        $sBaseUrl = $this->getBaseUrl();
+        if(empty($sBaseUrl))
+            return '';
+
+        return $sBaseUrl . $this->getItemAnchor($iItemId, true);
+    }
+
+    public function getListAnchor($bWithHash = false)
+    {
+        return ($bWithHash ? '#' : '') . sprintf($this->_sListAnchor, str_replace('_', '-', $this->getSystemName()), $this->getId());
+    }
+
+    public function getItemAnchor($iItemId, $bWithHash = false)
+    {
+        return ($bWithHash ? '#' : '') . sprintf($this->_sItemAnchor, str_replace('_', '-', $this->getSystemName()), $this->getId(), $iItemId);
+    }
+
+    public function getConnectionObject($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_CMT_FILTER_FRIENDS:
+                $sResult = $this->_sConnObjFriends;
+                break;
+            case BX_CMT_FILTER_SUBSCRIPTIONS:
+                $sResult = $this->_sConnObjSubscriptions;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    public function getVoteObject($iId)
+    {
+        if(empty($this->_aSystem['object_vote']))
+            $this->_aSystem['object_vote'] = 'sys_cmts';
+
+        $oVote = BxDolVote::getObjectInstance($this->_aSystem['object_vote'], $iId, true, $this->_oTemplate);
+        if(!$oVote || !$oVote->isEnabled())
+            return false;
+
+        return $oVote;
+    }
+
+    public function getReactionObject($iId)
+    {
+        if(empty($this->_aSystem['object_reaction']))
+            $this->_aSystem['object_reaction'] = 'sys_cmts_reactions';
+
+        $oReaction = BxDolVote::getObjectInstance($this->_aSystem['object_reaction'], $iId, true, $this->_oTemplate);
+        if(!$oReaction || !$oReaction->isEnabled())
+            return false;
+
+        return $oReaction;
+    }
+
+    public function getScoreObject($iId)
+    {
+        if(empty($this->_aSystem['object_score']))
+        	$this->_aSystem['object_score'] = 'sys_cmts';
+
+        $oScore = BxDolScore::getObjectInstance($this->_aSystem['object_score'], $iId, true, $this->_oTemplate);
+        if(!$oScore || !$oScore->isEnabled())
+            return false;
+
+        return $oScore;
+    }
+
+    public function getReportObject($iId)
+    {
+        if(empty($this->_aSystem['object_report']))
+        	$this->_aSystem['object_report'] = 'sys_cmts';
+
+        $oReport = BxDolReport::getObjectInstance($this->_aSystem['object_report'], $iId, true, $this->_oTemplate);
+        if(!$oReport || !$oReport->isEnabled())
+            return false;
+
+        return $oReport;
+    }
+
+    public function getNotificationId()
+    {
+        return 'cmts-notification-' . $this->_sSystem . '-' . $this->_iId;
+    }
+
+    /**
+     * @deprecated since version 10.0.0-B3 and can be removed in later versions.
+     */
+    public function setTableNameFiles($sTable)
+    {
+        $this->_sTableImages = $sTable;
+    	$this->_oQuery->setTableNameFiles($sTable);
+    }
+
+    /**
+     * @deprecated since version 10.0.0-B3 and can be removed in later versions.
+     */
+    public function setTableNameFiles2Entries($sTable)
+    {
+    	$this->_sTableImages2Entries = $sTable;
+    	$this->_oQuery->setTableNameFiles2Entries($sTable);
+    }
+
+    public function isHtml ()
+    {
+        return $this->_aSystem['html'] > 0;
+    }
+
+    public function isRatable ()
+    {
+        return $this->_aSystem['is_ratable'];
+    }
+
+    public function isAttachImageEnabled()
+    {
+        return true;
+    }
+
+    /**
+     * set id to operate with votes
+     */
+    public function setId ($iId)
+    {
+        if ($iId == $this->getId()) return;
+        $this->_iId = $iId;
+    }
+
+    /**
+     * Add replace markers.
+     * @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;
+    }
+
+    /**
+     * Database functions
+     */
+    public function getQueryObject ()
+    {
+        return $this->_oQuery;
+    }
+
+    public function getCommentsTableName ()
+    {
+        return $this->_oQuery->getTableName ();
+    }
+
+	public function getObjectAuthorId ($iObjectId = 0)
+    {
+    	if(empty($this->_aSystem['trigger_field_author']))
+    		return 0;
+
+        return $this->_oQuery->getObjectAuthorId ($iObjectId ? $iObjectId : $this->getId());
+    }
+
+    public function getObjectTitle ($iObjectId = 0)
+    {
+        if(empty($this->_aSystem['trigger_field_title']))
+            return '';
+
+        $sTitle = $this->_oQuery->getObjectTitle ($iObjectId ? $iObjectId : $this->getId());
+        if(get_mb_substr($sTitle, 0, 1) == '_')
+            $sTitle = _t($sTitle);
+
+        return $sTitle;
+    }
+
+    public function getCommentsCountAll ($iObjectId = 0)
+    {
+        return $this->_oQuery->getCommentsCountAll ($iObjectId ? $iObjectId : $this->getId());
+    }
+
+    public function getCommentsCount ($iObjectId = 0, $iCmtVParentId = -1, $sFilter = '')
+    {
+        return $this->_oQuery->getCommentsCount ($iObjectId ? $iObjectId : $this->getId(), $iCmtVParentId, $this->_getAuthorId(), $sFilter);
+    }
+
+    public function getCommentsArray ($iVParentId, $sFilter, $aOrder, $iStart = 0, $iCount = -1)
+    {
+        return $this->_oQuery->getComments ($this->getId(), $iVParentId, $this->_getAuthorId(), $sFilter, $aOrder, $iStart, $iCount);
+    }
+
+    /**
+     * Get comment's short info.
+     */
+    public function getCommentSimple ($iCmtId)
+    {
+        return $this->_oQuery->getCommentSimple ($this->getId(), $iCmtId);
+    }
+
+    /**
+     * Get comment's full info.
+     */
+    public function getCommentRow ($iCmtId)
+    {
+        return $this->_oQuery->getComment ($this->getId(), $iCmtId);
+    }
+
+    public function onObjectDelete ($iObjectId = 0)
+    {
+        // delete comments
+        $aFiles = $aCmtIds = array();
+        $this->_oQuery->deleteObjectComments ($iObjectId ? $iObjectId : $this->getId(), $aFiles, $aCmtIds);
+
+        // delete meta info
+        $this->deleteMetaInfo($aCmtIds);
+
+        // delete files
+        if ($aFiles) {
+            $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+            if ($oStorage)
+                $oStorage->queueFilesForDeletion($aFiles);
+        }
+    }
+
+    public static function onAuthorDelete ($iAuthorId)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            $o = self::getObjectInstance($sSystem, 0);
+            $oQuery = $o->getQueryObject();
+
+            // delete comments
+            $aFiles = $aCmtIds = array ();
+            $oQuery->deleteAuthorComments($iAuthorId, $aFiles, $aCmtIds);
+
+            // delete meta info
+            $o->deleteMetaInfo($aCmtIds);
+    
+            // delete files
+            $oStorage = BxDolStorage::getObjectInstance($o->getStorageObjectName());
+            if ($oStorage)
+                $oStorage->queueFilesForDeletion($aFiles);
+        }
+        return true;
+    }
+
+    public static function onModuleEnable ($sModuleName)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if ($sModuleName !== $aSystem['module'])
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $o->registerTranscoders();
+        }
+
+        return true;
+    }
+
+    public static function onModuleDisable ($sModuleName)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if ($sModuleName !== $aSystem['module'])
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $o->unregisterTranscoders();
+        }
+
+        return true;
+    }
+
+    public static function onModuleUninstall ($sModuleName, &$iFiles = null)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if ($sModuleName !== $aSystem['module'])
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $oQuery = $o->getQueryObject();
+
+            // delete comments
+            $aFiles = $aCmtIds = array ();
+            $oQuery->deleteAll($aSystem['system_id'], $aFiles, $aCmtIds);
+
+            // delete meta info
+            $o->deleteMetaInfo($aCmtIds);
+
+            // delete files
+            $oStorage = BxDolStorage::getObjectInstance($o->getStorageObjectName());
+            if ($oStorage && $aFiles)
+                $oStorage->queueFilesForDeletion($aFiles);
+
+            if (null !== $iFiles)
+                $iFiles += count($aFiles);
+        }
+
+        return true;
+    }
+
+    public function deleteMetaInfo ($mixedCmtId)
+    {
+        if(!is_array($mixedCmtId))
+            $mixedCmtId = array($mixedCmtId);
+
+        $oMetatags = false;
+        if(!empty($this->_sMetatagsObj))
+            $oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj);
+
+        foreach($mixedCmtId as $iCmtId) {
+            if($oMetatags)
+                $oMetatags->onDeleteContent($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId));
+
+            $this->_oQuery->deleteCmtIds($this->_aSystem['system_id'], $iCmtId);
+        }
+    }
+
+    /**
+     * Permissions functions
+     */
+    public function checkAction ($sAction, $isPerformAction = false)
+    {
+        $iId = $this->_getAuthorId();
+        $a = checkActionModule($iId, $sAction, 'system', $isPerformAction);
+        return $a[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkActionErrorMsg ($sAction)
+    {
+        $iId = $this->_getAuthorId();
+        $a = checkActionModule($iId, $sAction, 'system');
+        return $a[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED ? $a[CHECK_ACTION_MESSAGE] : '';
+    }
+
+    public function isViewAllowed ($isPerformAction = false)
+    {
+        $mixedResult = BxDolService::call($this->_aSystem['module'], 'check_allowed_comments_view', array($this->getId(), $this->getSystemName()));
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function isVoteAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(!$this->isRatable())
+            return false;
+
+        $oVote = $this->getVoteObject($aCmt['cmt_id']);
+        if($oVote === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oVote->isAllowedVote($isPerformAction);
+    }
+
+    public function isScoreAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(!$this->isRatable())
+            return false;
+
+        $oScore = $this->getScoreObject($aCmt['cmt_id']);
+        if($oScore === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oScore->isAllowedVote($isPerformAction);
+    }
+
+    public function isReportAllowed ($aCmt, $isPerformAction = false)
+    {
+        $oReport = $this->getReportObject($aCmt['cmt_id']);
+        if($oReport === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oReport->isAllowedReport($isPerformAction);
+    }
+
+    public function isPostAllowed ($isPerformAction = false)
+    {
+        $mixedResult = BxDolService::call($this->_aSystem['module'], 'check_allowed_comments_post', array($this->getId(), $this->getSystemName()));
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        return $this->checkAction ('comments post', $isPerformAction);
+    }
+
+    public function msgErrPostAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments post');
+    }
+
+    /**
+     * Determines whether a 'reply' action allowed or not.
+     * @param integer or array $mixedCmt - ID or an array which describes the comment to be replied
+     * @param boolean $isPerformAction
+     * @return boolean
+     */
+    public function isReplyAllowed ($mixedCmt, $isPerformAction = false)
+    {
+        return $this->isPostAllowed($isPerformAction);
+    }
+
+    public function msgErrReplyAllowed ()
+    {
+        return $this->msgErrPostAllowed ();
+    }
+
+    public function isQuoteAllowed ($mixedCmt, $isPerformAction = false)
+    {
+        return $this->_bPostQuote && $this->isReplyAllowed ($mixedCmt, $isPerformAction);
+    }
+
+    public function msgErrQuoteAllowed ()
+    {
+        return $this->msgErrReplyAllowed ();
+    }
+
+    public function isEditAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        if($this->isEditAllowedAll($isPerformAction))
+            return true;
+
+        $mixedResult = BxDolService::call($this->_aSystem['module'], 'check_allowed_comments_post', array($this->getId(), $this->getSystemName()));
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        return abs($aCmt['cmt_author_id']) == $this->_getAuthorId() && $this->checkAction ('comments edit own', $isPerformAction);
+    }
+
+    public function msgErrEditAllowed ()
+    {
+        return $this->checkActionErrorMsg ('comments edit own');
+    }
+
+    public function isEditAllowedAll ($isPerformAction = false)
+    {
+        return $this->checkAction('comments edit all', $isPerformAction);
+    }
+
+    public function isRemoveAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        if($this->isRemoveAllowedAll($isPerformAction))
+            return true;
+
+        return abs($aCmt['cmt_author_id']) == $this->_getAuthorId() && $this->checkAction ('comments remove own', $isPerformAction);
+    }
+
+    public function isRemoveAllowedAll ($isPerformAction = false)
+    {
+        return $this->checkAction ('comments remove all', $isPerformAction);
+    }
+
+    public function msgErrRemoveAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments remove own');
+    }
+
+    public function isMoreAllowed ($aCmt, $isPerformAction = false)
+    {
+        $oMenuManage = BxDolMenu::getObjectInstance($this->_sMenuObjManage);
+	    $oMenuManage->setCmtsData($this, $aCmt['cmt_id']);
+    	return $oMenuManage->isVisible();
+    }
+
+    public function isModerator()
+    {
+        return $this->isEditAllowedAll() || $this->isRemoveAllowedAll();
+    }
+
+    /**
+     * Actions functions
+     */
+    public function actionGetFormPost ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        $iCmtParentId = bx_get('CmtParent');
+        $iCmtParentId = $iCmtParentId !== false ? bx_process_input($iCmtParentId, BX_DATA_INT) : 0;
+        
+        $sCmtBrowse = bx_get('CmtBrowse');
+        $sCmtBrowse = $sCmtBrowse !== false ? bx_process_input($sCmtBrowse, BX_DATA_TEXT) : '';
+
+        $sCmtDisplay = bx_get('CmtDisplay');
+        $sCmtDisplay = $sCmtDisplay !== false ? bx_process_input($sCmtDisplay, BX_DATA_TEXT) : '';
+
+        $bQuote = bx_get('CmtQuote');
+        $bQuote = $bQuote !== false ? bx_process_input($bQuote, BX_DATA_INT) == 1 : false;
+
+        $bMinPostForm = bx_get('CmtMinPostForm');
+        $bMinPostForm = $bMinPostForm !== false ? bx_process_input($bMinPostForm, BX_DATA_INT) == 1 : $this->_bMinPostForm;
+
+        return $this->getFormBoxPost(array('parent_id' => $iCmtParentId, 'type' => $sCmtBrowse), array('type' => $sCmtDisplay, 'dynamic_mode' => true, 'quote' => $bQuote, 'min_post_form' => $bMinPostForm));
+    }
+
+    public function actionGetFormEdit ()
+    {
+        if (!$this->isEnabled())
+            return echoJson(array());
+
+        $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+        echoJson($this->getFormEdit($iCmtId, array('dynamic_mode' => true)));
+    }
+
+    public function actionGetCmt ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        if($this->isViewAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
+            return '';
+
+        $iCmtId = bx_process_input($_REQUEST['Cmt'], BX_DATA_INT);
+        $sCmtBrowse = isset($_REQUEST['CmtBrowse']) ? bx_process_input($_REQUEST['CmtBrowse'], BX_DATA_TEXT) : '';
+        $sCmtDisplay = isset($_REQUEST['CmtDisplay']) ? bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT) : '';
+
+        $aCmt = $this->getCommentRow($iCmtId);
+        echoJson(array(
+            'parent_id' => $aCmt['cmt_parent_id'],
+            'vparent_id' => $aCmt['cmt_vparent_id'],
+            'content' => $this->getComment($aCmt, array('type' => $sCmtBrowse), array('type' => $sCmtDisplay, 'dynamic_mode' => true))
+        ));
+    }
+
+    public function actionGetCmts ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        if($this->isViewAllowed() !== CHECK_ACTION_RESULT_ALLOWED)
+            return '';
+
+        $aBp = $aDp = array();
+        $this->_getParams($aBp, $aDp);
+
+        $aDp['dynamic_mode'] = true;
+        return $this->getComments($aBp, $aDp);
+    }
+
+    public function actionSubmitPostForm()
+    {
+        if(!$this->isEnabled())
+            return echoJson(array());
+
+        $iCmtParentId = 0;
+        if(bx_get('cmt_parent_id') !== false)
+            $iCmtParentId = bx_process_input(bx_get('cmt_parent_id'), BX_DATA_INT);
+
+        echoJson($this->getFormPost($iCmtParentId, array('dynamic_mode' => true)));
+    }
+
+    public function actionSubmitEditForm()
+    {
+        if (!$this->isEnabled()) {
+            echoJson(array());
+            return;
+        };
+
+        $iCmtId = 0;
+        if(bx_get('cmt_id') !== false)
+            $iCmtId = bx_process_input(bx_get('cmt_id'), BX_DATA_INT);
+
+        echoJson($this->getFormEdit($iCmtId, array('dynamic_mode' => true)));
+    }
+
+    public function actionRemove()
+    {
+        if (!$this->isEnabled()) {
+            echoJson(array());
+            return;
+        };
+
+        $iCmtId = 0;
+        if(bx_get('Cmt') !== false)
+            $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+        echoJson($this->remove($iCmtId));
+    }
+    
+    public function remove($iCmtId)
+    {
+        $aCmt = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iCmtId));
+        if(!$aCmt)
+            return array('msg' => _t('_No such comment'));
+        
+        $iObjId = $this->getId();
+        if(!$iObjId){
+            $this->setId($aCmt['cmt_object_id']);
+            $iObjId = $aCmt['cmt_object_id'];
+        }
+        $iObjAthrId = $this->getObjectAuthorId($iObjId);
+        
+        if($aCmt['cmt_replies'] > 0) {
+            if(!$this->isModerator())
+                return array('msg' => _t('_Can not delete comments with replies'));
+
+            $aReplies = $this->_oQuery->getCommentsBy(array('type' => 'parent_id', 'parent_id' => $iCmtId));
+            foreach($aReplies as $aReply)
+                $this->remove($aReply['cmt_id']);
+        }
+
+        $iPerformerId = $this->_getAuthorId();
+        if(!$this->isRemoveAllowed($aCmt))
+            return array('msg' => $aCmt['cmt_author_id'] == $iPerformerId ? strip_tags($this->msgErrRemoveAllowed()) : _t('_Access denied'));
+
+        if($this->_oQuery->removeComment($iObjId, $iCmtId, $aCmt['cmt_parent_id'])) {
+            $this->_triggerComment();
+
+            $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+
+            $aImages = $this->_oQuery->getFiles($this->_aSystem['system_id'], $iCmtId);
+            foreach($aImages as $aImage)
+                $oStorage->deleteFile($aImage['image_id']);
+
+            $this->_oQuery->deleteImages($this->_aSystem['system_id'], $iCmtId);
+
+            $this->isRemoveAllowed($aCmt, true);
+
+            $this->deleteMetaInfo ($iCmtId);
+
+            bx_alert($this->_sSystem, 'commentRemoved', $iObjId, $iPerformerId, array(
+                'object_author_id' => $iObjAthrId,
+
+                'comment_id' => $iCmtId, 
+                'comment_author_id' => $aCmt['cmt_author_id']
+            ));
+
+            bx_alert('comment', 'deleted', $iCmtId, $iPerformerId, array(
+                'object_system' => $this->_sSystem, 
+                'object_id' => $iObjId, 
+                'object_author_id' => $iObjAthrId,
+                
+                'comment_author_id' => $aCmt['cmt_author_id']
+            ));
+
+            if(!empty($aCmt['cmt_parent_id'])) {
+                $iCmtPrntId = (int)$aCmt['cmt_parent_id'];
+                $aCmtPrnt = $this->_oQuery->getCommentSimple($iObjId, $iCmtPrntId);
+                if(!empty($aCmtPrnt) && is_array($aCmtPrnt)) {
+                    bx_alert($this->_sSystem, 'replyRemoved', $iCmtPrntId, $iPerformerId, array(
+                        'object_id' => $iObjId, 
+                        'object_author_id' => $iObjAthrId,
+                        
+                        'parent_author_id' => $aCmtPrnt['cmt_author_id'],
+                        
+                        'comment_id' => $iCmtId, 
+                        'comment_author_id' => $aCmt['cmt_author_id'],
+                    ));
+
+                    bx_alert('reply', 'deleted', $iCmtId, $iPerformerId, array(
+                        'object_system' => $this->_sSystem, 
+                        'object_id' => $iObjId, 
+                        'object_author_id' => $iObjAthrId,
+
+                        'parent_id' => $iCmtPrntId,
+                        'parent_author_id' => $aCmtPrnt['cmt_author_id'],
+
+                        'comment_author_id' => $aCmt['cmt_author_id'], 
+                    ));
+                }
+            }
+            return array('id' => $iCmtId);
+        }
+        return array('msg' => _t('_cmt_err_cannot_perform_action'));
+    }
+
+    public function actionResumeLiveUpdate()
+    {
+    	$sKey = $this->getNotificationId();
+
+    	bx_import('BxDolSession');
+    	BxDolSession::getInstance()->unsetValue($sKey);
+    }
+
+	public function actionPauseLiveUpdate()
+    {
+    	$sKey = $this->getNotificationId();
+
+    	bx_import('BxDolSession');
+    	BxDolSession::getInstance()->setValue($sKey, 1);
+    }
+
+    public function onPostAfter($iCmtId)
+    {
+        $iObjId = (int)$this->getId();
+        $iObjAthrId = $this->getObjectAuthorId($iObjId);
+
+        $aCmt = $this->_oQuery->getCommentSimple($iObjId, $iCmtId);
+        if(empty($aCmt) || !is_array($aCmt))
+            return false;
+
+        $iCmtPrntId = (int)$aCmt['cmt_parent_id'];
+        $iPerformerId = (int)$aCmt['cmt_author_id'];
+        bx_alert($this->_sSystem, 'commentPost', $iObjId, $iPerformerId, array(
+            'object_author_id' => $iObjAthrId,
+
+            'comment_id' => $iCmtId, 
+            'comment_author_id' => $aCmt['cmt_author_id'], 
+            'comment_text' => $aCmt['cmt_text']
+        ));
+
+        bx_alert('comment', 'added', $iCmtId, $iPerformerId, array(
+            'object_system' => $this->_sSystem, 
+            'object_id' => $iObjId, 
+            'object_author_id' => $iObjAthrId,
+
+            'comment_author_id' => $aCmt['cmt_author_id'], 
+            'comment_text' => $aCmt['cmt_text']
+        ));
+
+        if(!empty($iCmtPrntId)) {
+            $aCmtPrnt = $this->_oQuery->getCommentSimple($iObjId, $iCmtPrntId);
+            if(!empty($aCmtPrnt) && is_array($aCmtPrnt)) {
+                bx_alert($this->_sSystem, 'replyPost', $iCmtPrntId, $iPerformerId, array(
+                    'object_id' => $iObjId, 
+                    'object_author_id' => $iObjAthrId,
+
+                    'parent_author_id' => $aCmtPrnt['cmt_author_id'],
+
+                    'comment_id' => $iCmtId,
+                    'comment_author_id' => $aCmt['cmt_author_id'], 
+                    'comment_text' => $aCmt['cmt_text']
+                ));
+
+                bx_alert('comment', 'replied', $iCmtId, $iPerformerId, array(
+                    'object_system' => $this->_sSystem, 
+                    'object_id' => $iObjId, 
+                    'object_author_id' => $iObjAthrId,
+
+                    'parent_id' => $iCmtPrntId,
+                    'parent_author_id' => $aCmtPrnt['cmt_author_id'],
+
+                    'comment_author_id' => $aCmt['cmt_author_id'],  
+                    'comment_text' => $aCmt['cmt_text']
+                ));
+            }
+        }
+
+        return array('id' => $iCmtId, 'parent_id' => $iCmtPrntId);
+    }
+
+    public function onEditAfter($iCmtId)
+    {
+        $iObjId = (int)$this->getId();
+    	$iObjAthrId = $this->getObjectAuthorId($iObjId);
+
+    	$aCmt = $this->_oQuery->getCommentSimple($iObjId, $iCmtId);
+        if(empty($aCmt) || !is_array($aCmt))
+            return false;
+
+        $iPerformerId = $this->_getAuthorId();
+        bx_alert($this->_sSystem, 'commentUpdated', $iObjId, $iPerformerId, array(
+            'object_author_id' => $iObjAthrId,
+
+            'comment_id' => $iCmtId, 
+            'comment_author_id' => $aCmt['cmt_author_id'], 
+            'comment_text' => $aCmt['cmt_text']        	
+        ));
+
+        bx_alert('comment', 'edited', $iCmtId, $iPerformerId, array(
+            'object_system' => $this->_sSystem, 
+            'object_id' => $iObjId, 
+            'object_author_id' => $iObjAthrId,
+
+            'comment_author_id' => $aCmt['cmt_author_id'],
+            'comment_text' => $aCmt['cmt_text']
+        ));
+
+        return array('id' => $iCmtId, 'text' => $this->_prepareTextForOutput($aCmt['cmt_text'], $iCmtId));
+    }
+
+    public function serviceGetAuthor ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $aComment['cmt_author_id'];
+    }
+
+    public function serviceGetDateAdded ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $aComment['cmt_time'];
+    }
+
+    public function serviceGetDateChanged ($iContentId)
+    {
+        return 0;
+    }
+    public function serviceGetLink ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $this->getViewUrl($iContentId);
+    }
+
+    public function serviceGetTitle ($iContentId)
+    {
+        return '';
+    }
+
+    public function serviceGetText ($iContentId)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return $aComment['cmt_text'];
+    }
+
+    public function serviceGetThumb ($iContentId)
+    {
+        return '';
+    }
+
+    public function serviceGetInfo ($iContentId, $bSearchableFieldsOnly = true)
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        $this->setId($aComment['cmt_object_id']);
+
+        return BxDolContentInfo::formatFields($aComment);
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        $aComment = $this->_oQuery->getCommentsBy(array('type' => 'id', 'id' => $iContentId));
+        if(empty($aComment) || !is_array($aComment))
+            return '';
+
+        $this->setId($aComment['cmt_object_id']);
+
+        return $this->getComment($aComment, array(), array('type' => BX_CMT_DISPLAY_FLAT, 'view_only' => true));
+    }
+
+    public function serviceGetAll ($aParams = array())
+    {
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array('type' => 'all');
+
+        return $this->_oQuery->getCommentsBy($aParams);
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $oForm = BxDolForm::getObjectInstance('sys_comment', 'sys_comment_post', $this->_oTemplate);
+        if(!$oForm)
+            return array();
+
+        $aSrchNamesExcept = array();
+        $aSrchCaptionsSystem = array(
+            'cmt_author_id' => '_sys_form_comment_input_caption_system_cmt_author_id',
+            'cmt_text' => '_sys_form_comment_input_caption_system_cmt_text'
+        );
+        $aSrchCaptions = array(
+            'cmt_author_id' => '_sys_form_comment_input_caption_cmt_author_id',
+            'cmt_text' => '_sys_form_comment_input_caption_cmt_text'
+        );
+
+        $aResult = array(
+            'cmt_author_id' => array(
+                'type' => 'text_auto', 
+                'caption_system' => $aSrchCaptionsSystem['cmt_author_id'],
+                'caption' => $aSrchCaptions['cmt_author_id'],
+                'info' => '',
+                'value' => '',
+                'values' => '',
+                'pass' => ''
+            )
+        );
+
+        $aInputs = $oForm->aInputs;
+        if(!empty($aInputsAdd) && is_array($aInputsAdd))
+            $aInputs = array_merge($aInputs, $aInputsAdd);
+
+        foreach($aInputs as $aInput)
+            if(in_array($aInput['type'], BxDolSearchExtended::$SEARCHABLE_TYPES) && !in_array($aInput['name'], $aSrchNamesExcept))
+                $aResult[$aInput['name']] = array(
+                    'type' => $aInput['type'], 
+                    'caption_system' => !empty($aInput['caption_system_src']) ? $aInput['caption_system_src'] : '',
+                    'caption' => !empty($aInput['caption_src']) ? $aInput['caption_src'] : (!empty($aSrchCaptions[$aInput['name']]) ? $aSrchCaptions[$aInput['name']] : ''),
+                    'info' => !empty($aInput['info_src']) ? $aInput['info_src'] : '',
+                    'value' => !empty($aInput['value']) ? $aInput['value'] : '',
+                    'values' => !empty($aInput['values_src']) ? $aInput['values_src'] : '',
+                    'pass' => !empty($aInput['db']['pass']) ? $aInput['db']['pass'] : ''
+                );
+
+        return $aResult;
+    }
+
+    /**
+     * Overwrite this method and register transcoder(s) if comments object uses custom transcoder(s), 
+     * which differs from default one 'sys_cmts_images_preview'
+     */
+    public function registerTranscoders()
+    {}
+
+	/**
+     * Overwrite this method and unregister transcoder(s) if comments object uses custom transcoder(s), 
+     * which differs from default one 'sys_cmts_images_preview'
+     */
+    public function unregisterTranscoders()
+    {}
+
+    public function serviceGetSearchResultExtended($aParams, $iStart = 0, $iPerPage = 0)
+    {
+        if(empty($aParams) || !is_array($aParams))
+            return array();
+
+        return $this->_oQuery->getCommentsBy(array('type' => 'search_ids', 'search_params' => $aParams, 'start' => $iStart, 'per_page' => $iPerPage));
+    }
+
+    public function getAuthorInfo($iAuthorId = 0)
+    {
+        return $this->_getAuthorInfo($iAuthorId);
+    }
+
+    /**
+     * Internal functions
+     */
+    protected function _getAuthorId ()
+    {
+        return isMember() ? bx_get_logged_profile_id() : 0;
+    }
+
+    protected function _getAuthorPassword ()
+    {
+        return isMember() ? $_COOKIE['memberPassword'] : "";
+    }
+
+    protected function _getAuthorIp ()
+    {
+        return getVisitorIP();
+    }
+
+    protected function _getAuthorInfo($iAuthorId = 0)
+    {
+        $iUserId = $this->_getAuthorId();
+        $iAuthorId = (int)$iAuthorId;
+        $oProfile = $this->_getAuthorObject($iAuthorId);
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+        );
+    }
+
+    protected function _getAuthorObject($iAuthorId = 0)
+    {
+        return BxDolProfile::getInstanceMagic($iAuthorId);
+    }
+
+	protected function _getFormObject($sAction = BX_CMT_ACTION_POST)
+    {
+        $sDisplayName = '_sFormDisplay' . ucfirst($sAction);
+
+        return BxDolForm::getObjectInstance($this->_sFormObject, $this->$sDisplayName, false, $this->_sSystem);
+    }
+
+    protected function _getParams(&$aBp, &$aDp)
+    {
+        //--- Process 'Browse' params. 
+        $aBp['parent_id'] = isset($aBp['parent_id']) ? (int)$aBp['parent_id'] : 0;
+
+        $aBp['vparent_id'] = isset($aBp['vparent_id']) ? (int)$aBp['vparent_id'] : 0;
+        if(bx_get('CmtParent') !== false)
+            $aBp['vparent_id'] = bx_process_input(bx_get('CmtParent'), BX_DATA_INT);
+
+    	$aBp['type'] = isset($aBp['type']) ? $aBp['type'] : '';
+    	if(bx_get('CmtBrowse') !== false) 
+        	$aBp['type'] = bx_process_input(bx_get('CmtBrowse'), BX_DATA_TEXT);
+
+    	$aBp['filter'] = isset($aBp['filter']) ? $aBp['filter'] : '';
+    	if(bx_get('CmtFilter') !== false) 
+    	    $aBp['filter'] = bx_process_input(bx_get('CmtFilter'), BX_DATA_TEXT);
+
+        $aBp['start'] = isset($aBp['start']) ? (int)$aBp['start'] : -1;
+        if(bx_get('CmtStart') !== false) 
+            $aBp['start'] = bx_process_input($_REQUEST['CmtStart'], BX_DATA_INT);
+
+        $aBp['per_view'] = isset($aBp['per_view']) ? (int)$aBp['per_view'] : -1;
+        if(bx_get('CmtPerView') !== false) 
+            $aBp['per_view'] = bx_process_input($_REQUEST['CmtPerView'], BX_DATA_INT);
+
+        //--- Process 'Display' params.
+        $aDp['type'] = isset($aDp['type']) ? $aDp['type'] : '';
+        if(bx_get('CmtDisplay') !== false) 
+            $aDp['type'] = bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT);
+
+        $aDp['blink'] = isset($aDp['blink']) ? $aDp['blink'] : '';
+        if(bx_get('CmtBlink') !== false) 
+            $aDp['blink'] = bx_process_input($_REQUEST['CmtBlink'], BX_DATA_TEXT);
+
+        $aDp['quote'] = isset($aDp['quote']) ? (int)$aDp['quote'] : 0;
+        if(bx_get('CmtQuote') !== false) 
+            $aDp['quote'] = bx_process_input($_REQUEST['CmtQuote'], BX_DATA_INT);
+
+        $aDp['min_post_form'] = isset($aDp['min_post_form']) ? (bool)$aDp['min_post_form'] : $this->_bMinPostForm;
+        if(bx_get('CmtMinPostForm') !== false)
+            $aDp['min_post_form'] = bx_process_input(bx_get('CmtMinPostForm'), BX_DATA_INT) == 1;
+
+        $aDp['in_designbox'] = isset($aDp['in_designbox']) ? (bool)$aDp['in_designbox'] : true;
+        $aDp['dynamic_mode'] = isset($aDp['dynamic_mode']) ? (bool)$aDp['dynamic_mode'] : false;
+        $aDp['show_empty'] = isset($aDp['show_empty']) ? (bool)$aDp['show_empty'] : false;
+    }
+
+    protected function _prepareTextForOutput ($s, $iCmtId = 0)
+    {
+    	$iDataAction = !$this->isHtml() ? BX_DATA_TEXT_MULTILINE : BX_DATA_HTML;
+    	$s = bx_process_output($s, $iDataAction);
+    	$s = bx_linkify_html($s, 'class="' . BX_DOL_LINK_CLASS . '"');
+
+        if($iCmtId && $this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+            $s = $oMetatags->metaParse($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId), $s);
+
+        return $s;
+    }
+
+    protected function _prepareParams(&$aBp, &$aDp)
+    {
+        $aBp['type'] = !empty($aBp['type']) ? $aBp['type'] : $this->_sBrowseType;
+        $aBp['filter'] = !empty($aBp['filter']) ? $aBp['filter'] : $this->_sBrowseFilter;
+        $aBp['parent_id'] = isset($aBp['parent_id']) ? $aBp['parent_id'] : 0;
+        $aBp['start'] = isset($aBp['start']) ? $aBp['start'] : -1;
+        $aBp['per_view'] = isset($aBp['per_view']) ? $aBp['per_view'] : -1;
+        $aBp['order']['by'] = isset($aBp['order_by']) ? $aBp['order_by'] : $this->_aOrder['by'];
+        $aBp['order']['way'] = isset($aBp['order_way']) ? $aBp['order_way'] : $this->_aOrder['way'];
+
+        $aDp['type'] = !empty($aDp['type']) ? $aDp['type'] : $this->_sDisplayType;
+        $aDp['blink'] = !empty($aDp['blink']) ? $aDp['blink'] : array();
+        if(!is_array($aDp['blink']))
+            $aDp['blink'] = explode(',', $aDp['blink']);
+        $aDp['quote'] = !empty($aDp['quote']) ? (int)$aDp['quote'] : 0;
+        if(!isset($aDp['min_post_form']))
+            $aDp['min_post_form'] = $this->_bMinPostForm;
+
+        switch($aDp['type']) {
+            case BX_CMT_DISPLAY_FLAT:
+                $aBp['vparent_id'] = -1;
+                $aBp['per_view'] = $aBp['per_view'] != -1 ? $aBp['per_view'] : $this->getPerView(0);
+                break;
+
+            case BX_CMT_DISPLAY_THREADED:
+                $aBp['per_view'] = $aBp['per_view'] != -1 ? $aBp['per_view'] : $this->getPerView($aBp['vparent_id']);
+                break;
+        }
+
+        switch ($aBp['type']) {
+            case BX_CMT_BROWSE_POPULAR:
+                $aBp['order'] = array(
+                    'by' => BX_CMT_ORDER_BY_POPULAR,
+                    'way' => BX_CMT_ORDER_WAY_DESC
+                );
+                break;
+        }
+
+        $aBp['count'] = $this->getCommentsCount($this->_iId, $aBp['vparent_id'], $aBp['filter']);
+        if($aBp['start'] != -1)
+            return;
+
+        $aBp['start'] = 0;
+        if($aBp['type'] == BX_CMT_BROWSE_TAIL) {
+            $aBp['start'] = $aBp['count'] - $aBp['per_view'];
+            if($aBp['start'] < 0) {
+                $aBp['per_view'] += $aBp['start'];
+                $aBp['start'] = 0;
+            }
+        }
+
+        $this->_setUserChoice($aDp['type'], $aBp['type'], $aBp['filter']);
+    }
+
+    protected function _triggerComment()
+    {
+        if(!$this->_aSystem['trigger_table'])
+            return false;
+
+        $iId = $this->getId();
+        if(!$iId)
+            return false;
+
+        $iCount = $this->getCommentsCount($iId);
+        return $this->_oQuery->updateTriggerTable($iId, $iCount);
+    }
+
+    /**
+     * 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);
+    }
+
+    protected function _getUserChoice()
+    {
+        $mixedDp = $mixedBpType = $mixedBpFilter = false;
+        if(!isLogged())
+            return array($mixedDp, $mixedBpType, $mixedBpFilter);
+
+        $iUserId = $this->_getAuthorId();
+
+        $oSession = BxDolSession::getInstance();
+
+        $mixedDp = $oSession->getValue($this->_sDpSessionKey . $iUserId);
+        $mixedBpType = $oSession->getValue($this->_sBpSessionKeyType . $iUserId);
+        $mixedBpFilter = $oSession->getValue($this->_sBpSessionKeyFilter . $iUserId);
+
+        return array($mixedDp, $mixedBpType, $mixedBpFilter);
+    }
+
+    protected function _setUserChoice($sDp, $sBpType, $sBpFilter)
+    {
+        if(!isLogged())
+            return;
+
+        $iUserId = $this->_getAuthorId();
+
+        $oSession = BxDolSession::getInstance();
+
+        if(!empty($sDp))
+            $oSession->setValue($this->_sDpSessionKey . $iUserId, $sDp);
+
+        if(!empty($sBpType))
+            $oSession->setValue($this->_sBpSessionKeyType . $iUserId, $sBpType);
+
+        if(!empty($sBpFilter))
+            $oSession->setValue($this->_sBpSessionKeyFilter . $iUserId, $sBpFilter);
+    }
+
+    protected function _sendNotificationEmail($iCmtId, $iCmtParentId)
+    {
+        $aCmt = $this->getCommentRow($iCmtId);
+        $aCmtParent = $this->getCommentRow($iCmtParentId);
+        if(empty($aCmt) || !is_array($aCmt) || empty($aCmtParent) || !is_array($aCmtParent) || (int)$aCmt['cmt_author_id'] == (int)$aCmtParent['cmt_author_id'])
+            return;
+
+        $oProfile = $this->_getAuthorObject($aCmtParent['cmt_author_id']);
+
+        if($oProfile instanceof BxDolProfileUndefined)
+        	return;
+
+        $iAccount = $oProfile->getAccountId();
+        $aAccount = BxDolAccount::getInstance($iAccount)->getInfo();
+
+        $aPlus = array();
+        $aPlus['sender_display_name'] = $oProfile->getDisplayName();
+        $aPlus['reply_text'] = $this->_prepareTextForOutput($aCmt['cmt_text'], $iCmtId);
+
+        $sPageUrl = $this->getBaseUrl();
+        if(empty($sPageUrl))
+            $sPageUrl = $this->getViewUrl($iCmtParentId);
+        else 
+            $sPageUrl .= $this->getItemAnchor($iCmtParentId, true);
+        $aPlus['page_url'] = $sPageUrl;
+
+        $sPageTitle = $this->getObjectTitle();
+        if(empty($sPageTitle))
+            $sPageTitle = _t('_Content');
+        $aPlus['page_title'] = $sPageTitle;
+
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_CommentReplied', $aPlus);
+        return $aTemplate && sendMail($aAccount['email'], $aTemplate['Subject'], $aTemplate['Body']);
+    }
+
+    protected function _isShowDoComment($aParams, $isAllowedComment, $bCount)
+    {
+        $bShowDoComment = !isset($aParams['show_do_comment']) || $aParams['show_do_comment'] == true;
+
+        return $bShowDoComment && ($isAllowedComment || $bCount);
+    }
+
+    protected function _isShowCounter($aParams, $isAllowedComment, $bCount)
+    {
+        $bShowCounter = isset($aParams['show_counter']) && $aParams['show_counter'] === true;
+
+        return $bShowCounter && ($isAllowedComment || $bCount);
+    }
+
+    /**
+     * Note. By default image based controls aren't used.
+     * Therefore it can be overwritten in custom template.
+     */
+    protected function _getImageDo()
+    {
+    	return '';
+    }
+
+    protected function _getIconDo()
+    {
+    	return 'far comment';
+    }
+
+    protected function _getTitleDo()
+    {
+    	return '_cmt_txt_do';
+    }
+}
+
+/** @} */

+ 533 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCmtsQuery.php

@@ -0,0 +1,533 @@
+<?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
+ * @{
+ */
+
+/**
+ * @see BxDolCmts
+ */
+class BxDolCmtsQuery extends BxDolDb
+{
+    protected $_oMain;
+
+    protected $_sTable;
+    protected $_sTriggerTable;
+    protected $_sTriggerFieldId;
+    protected $_sTriggerFieldAuthor;
+    protected $_sTriggerFieldTitle;
+    protected $_sTriggerFieldComments;
+
+    protected $_sTableFiles;
+    protected $_sTableFiles2Entries;
+
+    protected $_sTableIds;
+
+    public function __construct(&$oMain)
+    {
+        $this->_sTableIds = BxDolCmts::$sTableIds;
+
+        $this->_oMain = $oMain;
+
+        $this->_sTableFiles = $this->_oMain->getTableNameImages();
+        $this->_sTableFiles2Entries = $this->_oMain->getTableNameImages2Entries();
+
+        $aSystem = $this->_oMain->getSystemInfo();
+        $this->_sTable = $aSystem['table'];
+        $this->_sTriggerTable = $aSystem['trigger_table'];
+        $this->_sTriggerFieldId = $aSystem['trigger_field_id'];
+        $this->_sTriggerFieldAuthor = $aSystem['trigger_field_author'];
+        $this->_sTriggerFieldTitle = $aSystem['trigger_field_title'];
+        $this->_sTriggerFieldComments = $aSystem['trigger_field_comments'];
+
+        parent::__construct();
+    }
+
+    function getTableName ()
+    {
+        return $this->_sTable;
+    }
+
+    /**
+     * @deprecated since version 10.0.0-B3 and can be removed in later versions.
+     */
+    function setTableNameFiles($sTable)
+    {
+    	$this->_sTableFiles = $sTable;
+    }
+
+    /**
+     * @deprecated since version 10.0.0-B3 and can be removed in later versions.
+     */
+    function setTableNameFiles2Entries($sTable)
+    {
+    	$this->_sTableFiles2Entries = $sTable;
+    }
+
+    function getCommentsCountAll ($iId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->_sTriggerFieldComments}` FROM `{$this->_sTriggerTable}` WHERE `{$this->_sTriggerFieldId}` = ?", $iId);
+        return (int)$this->getOne($sQuery);
+    }
+
+    function getCommentsCount ($iId, $iCmtVParentId = -1, $iAuthorId = 0, $sFilter = '')
+    {
+    	$aBindings = array(
+    		'cmt_object_id' => $iId
+    	);
+
+        $sWhereClause = '';
+        if((int)$iCmtVParentId >= 0) {
+        	$aBindings['cmt_vparent_id'] = $iCmtVParentId;
+
+            $sWhereClause .= " AND `{$this->_sTable}`.`cmt_vparent_id` = :cmt_vparent_id";
+        }
+
+        $sJoinClause = '';
+        switch($sFilter) {
+        	case BX_CMT_FILTER_FRIENDS:
+        	case BX_CMT_FILTER_SUBSCRIPTIONS:
+	            $oConnection = BxDolConnection::getObjectInstance($this->_oMain->getConnectionObject($sFilter));
+	
+	            $aQueryParts = $oConnection->getConnectedContentAsSQLParts($this->_sTable, 'cmt_author_id', $iAuthorId);
+	            $sJoinClause .= ' ' . $aQueryParts['join'];
+	            break;
+
+        	case BX_CMT_FILTER_OTHERS:
+        		$aBindings['cmt_author_id'] = $iAuthorId;
+
+        		$sWhereClause .= " AND `{$this->_sTable}`.`cmt_author_id` <> :cmt_author_id";
+        		break;
+        }
+
+        $sQuery = "SELECT COUNT(*) FROM `{$this->_sTable}` $sJoinClause WHERE `{$this->_sTable}`.`cmt_object_id` = :cmt_object_id" . $sWhereClause;
+        return (int)$this->getOne($sQuery, $aBindings);
+    }
+
+    function getComments ($iId, $iCmtVParentId = 0, $iAuthorId = 0, $sFilter = '', $aOrder = array(), $iStart = 0, $iCount = -1)
+    {
+    	$aBindings = array(
+    		'cmt_object_id' => $iId,
+            'system_id' => $this->_oMain->getSystemId()
+    	);
+        $sFields = $sJoin = "";
+        
+        $sWhereParent = '';
+        if((int)$iCmtVParentId >= 0) {
+        	$aBindings['cmt_vparent_id'] = $iCmtVParentId;
+
+            $sWhereParent = " AND `{$this->_sTable}`.`cmt_vparent_id` = :cmt_vparent_id";
+        }
+
+        if(in_array($sFilter, array(BX_CMT_FILTER_FRIENDS, BX_CMT_FILTER_SUBSCRIPTIONS))) {
+            $oConnection = BxDolConnection::getObjectInstance($this->_oMain->getConnectionObject($sFilter));
+
+            $aQueryParts = $oConnection->getConnectedContentAsSQLParts($this->_sTable, 'cmt_author_id', $iAuthorId);
+            $sJoin .= ' ' . $aQueryParts['join'];
+        }
+
+        $sOrder = " ORDER BY `{$this->_sTable}`.`cmt_time` ASC";
+        if(isset($aOrder['by']) && isset($aOrder['way'])) {
+            $aOrder['way'] = strtoupper(in_array($aOrder['way'], array(BX_CMT_ORDER_WAY_ASC, BX_CMT_ORDER_WAY_DESC)) ? $aOrder['way'] : BX_CMT_ORDER_WAY_ASC);
+
+            switch($aOrder['by']) {
+                case BX_CMT_ORDER_BY_DATE:
+                    $sOrder = " ORDER BY `{$this->_sTable}`.`cmt_time` " . $aOrder['way'];
+                    break;
+
+                case BX_CMT_ORDER_BY_POPULAR:
+                    $oVote = $this->_oMain->getVoteObject(0);
+                    $oScore = $this->_oMain->getScoreObject(0);
+                    $aSortFields = array();
+                    if($oVote !== false) {
+                        array_push($aSortFields, '`' . $this->_sTableIds . '`.`votes`');
+                    }
+                    if($oScore !== false) {
+                        array_push($aSortFields, '`' . $this->_sTableIds . '`.`score`');
+                    }
+                    if (count($aSortFields) == 0)
+                        array_push($aSortFields, '`' . $this->_sTable . '`.`id`');
+                    
+                    $sOrder = " ORDER BY " . implode($aOrder['way'] . ', ', $aSortFields) . " " . $aOrder['way'];
+                    break;
+            }
+        }
+        
+       	$sLimit = $iCount != -1 ? $this->prepareAsString(" LIMIT ?, ?", (int)$iStart, (int)$iCount) : '';
+
+        $sQuery = "SELECT
+                `{$this->_sTable}`.*,
+                `{$this->_sTableIds}`.`id` AS `cmt_unique_id`
+                $sFields
+            FROM `{$this->_sTable}`
+            LEFT JOIN `{$this->_sTableIds}` ON (`{$this->_sTable}`.`cmt_id` = `{$this->_sTableIds}`.`cmt_id` AND `{$this->_sTableIds}`.`system_id` = :system_id)
+            $sJoin
+            WHERE `{$this->_sTable}`.`cmt_object_id` = :cmt_object_id" . $sWhereParent . $sOrder . $sLimit;
+        return $this->getAll($sQuery, $aBindings);
+    }
+
+    function getCommentsBy($aParams = array())
+    {
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query', 1 => array()));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        $sSelectClause = "`{$this->_sTable}`.*";
+
+        if(isset($aParams['object_id'])) {
+            $aMethod['params'][1]['cmt_object_id'] = (int)$aParams['object_id'];
+
+            $sWhereClause .= " AND `{$this->_sTable}`.`cmt_object_id` = :cmt_object_id";
+        }
+
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1]['id'] = (int)$aParams['id'];
+
+                $sWhereClause .= " AND `{$this->_sTable}`.`cmt_id` = :id";
+                break;
+
+            case 'latest':
+            	if(!empty($aParams['author'])) {
+            		$aMethod['params'][1]['cmt_author_id'] = (int)$aParams['author'];
+
+            		$sWhereClause .= " AND `{$this->_sTable}`.`cmt_author_id` " . (isset($aParams['others']) && (int)$aParams['others'] == 1 ? "<>" : "=") . " :cmt_author_id";
+            	}
+
+                $sOrderClause = "`{$this->_sTable}`.`cmt_time` DESC";
+                $sLimitClause = "";
+                if(isset($aParams['per_page']))
+                	$sLimitClause = $this->prepareAsString("?, ?", $aParams['start'], $aParams['per_page']);
+
+                break;
+
+            case 'parent_id':
+                $aMethod['params'][1]['cmt_parent_id'] = (int)$aParams['parent_id'];
+
+                $sWhereClause .= " AND `{$this->_sTable}`.`cmt_parent_id` = :cmt_parent_id";
+
+                $sOrderClause = "`{$this->_sTable}`.`cmt_time` ASC";
+                $sLimitClause = "";
+                if(isset($aParams['per_page']))
+                    $sLimitClause = $this->prepareAsString("?, ?", $aParams['start'], $aParams['per_page']);
+
+                break;
+
+            case 'object_id':
+                $aMethod['params'][1]['cmt_object_id'] = (int)$aParams['object_id'];
+
+                $sWhereClause .= " AND `{$this->_sTable}`.`cmt_object_id` = :cmt_object_id";
+
+                $sOrderClause = "`{$this->_sTable}`.`cmt_time` ASC";
+                $sLimitClause = "";
+                if(isset($aParams['per_page']))
+                    $sLimitClause = $this->prepareAsString("?, ?", $aParams['start'], $aParams['per_page']);
+
+                break;
+
+            case 'author_id':
+                $aMethod['params'][1]['cmt_author_id'] = (int)$aParams['author_id'];
+
+                $sWhereClause .= " AND `{$this->_sTable}`.`cmt_author_id` = :cmt_author_id";
+
+                $sOrderClause = "`{$this->_sTable}`.`cmt_time` ASC";
+                $sLimitClause = "";
+                if(isset($aParams['per_page']))
+                    $sLimitClause = $this->prepareAsString("?, ?", $aParams['start'], $aParams['per_page']);
+
+                break;
+
+            case 'search_ids':
+                $aMethod['name'] = 'getColumn';
+
+                $sSelectClause = "`{$this->_sTable}`.`cmt_id`";
+
+                if (!empty($aParams['start']) && !empty($aParams['per_page']))
+                    $sLimitClause = $this->prepareAsString("?, ?", $aParams['start'], $aParams['per_page']);
+                elseif (!empty($aParams['per_page']))
+                    $sLimitClause = $this->prepareAsString("?", $aParams['per_page']);
+
+                $sWhereConditions = "1";
+                foreach($aParams['search_params'] as $sSearchParam => $aSearchParam) {
+                    $sSearchValue = "";
+                    switch ($aSearchParam['operator']) {
+                        case 'like':
+                            $sSearchValue = " LIKE " . $this->escape("%" . $aSearchParam['value'] . "%");
+                            break;
+
+                        case 'in':
+                            $sSearchValue = " IN (" . $this->implode_escape($aSearchParam['value']) . ")";
+                            break;
+
+                        case 'and':
+                            $iResult = 0;
+                            if (is_array($aSearchParam['value']))
+                                foreach ($aSearchParam['value'] as $iValue)
+                                    $iResult |= pow (2, $iValue - 1);
+                            else 
+                                $iResult = (int)$aSearchParam['value'];
+
+                            $sSearchValue = " & " . $iResult . "";
+                            break;
+
+                        default:
+                             $sSearchValue = " " . $aSearchParam['operator'] . " :" . $sSearchParam;
+                             $aMethod['params'][1][$sSearchParam] = $aSearchParam['value'];                             
+                    }
+
+                    $sWhereConditions .= " AND `{$this->_sTable}`.`" . $sSearchParam . "`" . $sSearchValue;
+                }
+
+                $sWhereClause .= " AND (" . $sWhereConditions . ")"; 
+
+                $sOrderClause .=  "`{$this->_sTable}`.`cmt_time` ASC";
+                break;
+
+            case 'all_ids':
+                $aMethod['name'] = 'getColumn';
+
+                $sSelectClause = "`{$this->_sTable}`.`cmt_id`";
+                $sOrderClause =  "`{$this->_sTable}`.`cmt_time` ASC";
+                break;
+        }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = 'ORDER BY ' . $sOrderClause;
+
+        if(!empty($sLimitClause))
+            $sLimitClause = 'LIMIT ' . $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `{$this->_sTable}` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+		return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    function getComment ($iId, $iCmtId)
+    {
+        $sFields = $sJoin = "";
+
+        $oVote = $this->_oMain->getVoteObject($iCmtId);
+        if($oVote !== false) {
+            $aSql = $oVote->getSqlParts($this->_sTableIds, 'id');
+
+            $sFields .= $aSql['fields'];
+            $sJoin .= $aSql['join'];
+        }
+
+        $sQuery = $this->prepare("SELECT
+                `{$this->_sTable}`.*,
+                `{$this->_sTableIds}`.`id` AS `cmt_unique_id`
+                $sFields
+            FROM `{$this->_sTable}`
+            LEFT JOIN `{$this->_sTableIds}` ON (`{$this->_sTable}`.`cmt_id` = `{$this->_sTableIds}`.`cmt_id` AND `{$this->_sTableIds}`.`system_id` = ?)
+            $sJoin
+            WHERE `{$this->_sTable}`.`cmt_object_id` = ? AND `{$this->_sTable}`.`cmt_id` = ?
+            LIMIT 1", $this->_oMain->getSystemId(), $iId, $iCmtId);
+        return $this->getRow($sQuery);
+    }
+
+    function getCommentSimple ($iId, $iCmtId)
+    {
+        $sQuery = $this->prepare("SELECT * FROM {$this->_sTable} AS `c` WHERE `cmt_object_id` = ? AND `cmt_id` = ? LIMIT 1", $iId, $iCmtId);
+        return $this->getRow($sQuery);
+    }
+
+    function removeComment ($iId, $iCmtId, $iCmtParentId)
+    {
+        $sQuery = $this->prepare("DELETE FROM {$this->_sTable} WHERE `cmt_object_id` = ? AND `cmt_id` = ? LIMIT 1", $iId, $iCmtId);
+        if (!$this->query($sQuery))
+            return false;
+
+        if($iCmtParentId)
+            $this->updateRepliesCount($iCmtParentId, -1);
+
+        return true;
+    }
+
+    function saveImages($iSystemId, $iCmtId, $iImageId)
+    {
+        $sQuery = $this->prepare("INSERT IGNORE INTO `{$this->_sTableFiles2Entries}` SET `system_id`=?, `cmt_id`=?, `image_id`=?", $iSystemId, $iCmtId, $iImageId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    function getFiles($iSystemId, $iCmtId, $iId = false)
+    {
+    	$aBindings = array(
+    		'system_id' => $iSystemId
+    	);
+
+        $sJoin = "";
+        $sWhere = " AND `tf2e`.`system_id` = :system_id ";
+
+        if($iCmtId !== false) {
+        	$aBindings['cmt_id'] = $iCmtId;
+
+            $sWhere .= " AND `tf2e`.`cmt_id` = :cmt_id ";
+        }
+
+        if($iId !== false) {
+        	$aBindings['cmt_object_id'] = $iId;
+
+            $sWhere .= " AND `te`.`cmt_object_id` = :cmt_object_id";
+            $sJoin .= " INNER JOIN `{$this->_sTable}` AS `te` ON (`tf2e`.`cmt_id` = `te`.`cmt_id`) ";
+        }
+
+        $sQuery = "SELECT 
+        		`tf2e`.*,
+        		`tf`.`file_name` AS `file_name`,
+        		`tf`.`mime_type` AS `mime_type`,
+        		`tf`.`size` AS `size` 
+        	FROM `{$this->_sTableFiles2Entries}` AS `tf2e` 
+        	LEFT JOIN `{$this->_sTableFiles}` AS `tf` ON (`tf2e`.`image_id` = `tf`.`id`) " . $sJoin . " 
+        	WHERE 1 " . $sWhere;
+
+        return $this->getAll($sQuery, $aBindings);
+    }
+
+    function deleteImages($iSystemId, $iCmtId)
+    {
+    	$aBindings = array(
+    		'system_id' => $iSystemId
+    	);
+
+        $sWhereAddon = "";
+        if ($iCmtId !== false) {
+        	$aBindings['cmt_id'] = $iCmtId;
+
+            $sWhereAddon = " AND `cmt_id` = :cmt_id ";
+        }
+
+        return $this->query("DELETE FROM `{$this->_sTableFiles2Entries}` WHERE `system_id` = :system_id" . $sWhereAddon, $aBindings);
+    }
+
+    function updateRepliesCount($iCmtId, $iCount)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `cmt_replies`=`cmt_replies`+? WHERE `cmt_id`=? LIMIT 1", $iCount, $iCmtId);
+        return $this->query($sQuery);
+    }
+
+    function deleteAuthorComments ($iAuthorId, &$aFiles = null, &$aCmtIds = null)
+    {
+        $aSystem = $this->_oMain->getSystemInfo();
+
+        $isDelOccured = 0;
+        $sQuery = $this->prepare("SELECT `cmt_id`, `cmt_parent_id` FROM {$this->_sTable} WHERE `cmt_author_id` = ? AND `cmt_replies` = 0", $iAuthorId);
+        $a = $this->getAll ($sQuery);
+		foreach ($a as $r) {
+            $sQuery = $this->prepare("DELETE FROM {$this->_sTable} WHERE `cmt_id` = ?", $r['cmt_id']);
+            $this->query ($sQuery);
+
+            $sQuery = $this->prepare("UPDATE {$this->_sTable} SET `cmt_replies` = `cmt_replies` - 1 WHERE `cmt_id` = ?", $r['cmt_parent_id']);
+            $this->query ($sQuery);
+
+            $aFilesMore = $this->convertImagesArray($this->getFiles($aSystem['system_id'], $r['cmt_id']));
+            $this->deleteImages($aSystem['system_id'], $r['cmt_id']);
+            if ($aFilesMore && null !== $aFiles)
+                $aFiles = array_merge($aFiles, $aFilesMore);
+
+            if (null !== $aCmtIds)
+                $aCmtIds[] = $r['cmt_id'];
+
+            $isDelOccured = 1;
+        }
+        $sQuery = $this->prepare("UPDATE {$this->_sTable} SET `cmt_author_id` = 0 WHERE `cmt_author_id` = ? AND `cmt_replies` != 0", $iAuthorId);
+        $this->query ($sQuery);
+        if ($isDelOccured)
+            $this->query ("OPTIMIZE TABLE {$this->_sTable}");
+    }
+
+    function deleteObjectComments ($iObjectId, &$aFilesReturn = null, &$aCmtIds = null)
+    {
+        $aSystem = $this->_oMain->getSystemInfo();
+        $aFiles = $this->convertImagesArray($this->getFiles($aSystem['system_id'], false, $iObjectId));
+
+        if ($aFiles) {
+            $sQuery = $this->prepare("DELETE FROM {$this->_sTableFiles2Entries} WHERE `system_id` = ? AND `image_id` IN(" . $this->implode_escape($aFiles) . ")", $aSystem['system_id']);
+            $this->query($sQuery);
+        }
+
+        if (null !== $aCmtIds) {
+            $sQuery = $this->prepare("SELECT `cmt_id` FROM {$this->_sTable} WHERE `cmt_object_id` = ?", $iObjectId);
+            $aCmtIds = $this->getColumn ($sQuery);
+        }
+
+        $sQuery = $this->prepare("DELETE FROM {$this->_sTable} WHERE `cmt_object_id` = ?", $iObjectId);
+        $this->query ($sQuery);
+        $this->query ("OPTIMIZE TABLE {$this->_sTable}");
+
+        if (null !== $aFilesReturn)
+            $aFilesReturn = $aFiles;
+    }
+
+    function deleteAll ($iSystemId, &$aFiles = null, &$aCmtIds = null)
+    {
+        // get files
+        if (null !== $aFiles)
+            $aFiles = $this->convertImagesArray($this->getFiles($iSystemId, false));
+
+        // delete files
+        $this->deleteImages($iSystemId, false);
+
+        if (null !== $aCmtIds)
+            $aCmtIds = $this->getColumn ("SELECT `cmt_id` FROM {$this->_sTable}");
+
+        // delete comments
+        $sQuery = $this->prepare("TRUNCATE TABLE {$this->_sTable}");
+        $this->query ($sQuery);
+    }
+
+    function deleteCmtIds ($iSystemId, $iCmtId)
+    {
+        $sQuery = $this->prepare("DELETE FROM {$this->_sTableIds} WHERE `system_id` = ? AND `cmt_id` = ?", $iSystemId, $iCmtId);
+        return $this->query ($sQuery);
+    }
+
+	function getObjectAuthorId($iId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->_sTriggerFieldAuthor}` FROM `{$this->_sTriggerTable}` WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iId);
+        return $this->getOne($sQuery);
+    }
+
+    function getObjectTitle($iId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->_sTriggerFieldTitle}` FROM `{$this->_sTriggerTable}` WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iId);
+        return $this->getOne($sQuery);
+    }
+
+    function updateTriggerTable($iId, $iCount)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldComments}` = ? WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iCount, $iId);
+        return $this->query($sQuery);
+    }
+
+    function getUniqId($iSystemId, $iCmtId, $iAuthorId = 0)
+    {
+        $sQuery = $this->prepare("SELECT `id` FROM `{$this->_sTableIds}` WHERE `system_id` = ? AND `cmt_id` = ?", $iSystemId, $iCmtId);
+        if(($iUniqId = (int)$this->getOne($sQuery)) != 0)
+            return $iUniqId;
+
+        if(empty($iAuthorId))
+            $iAuthorId = bx_get_logged_profile_id();
+
+        $sQuery = $this->prepare("INSERT INTO `{$this->_sTableIds}` SET `system_id` = ?, `cmt_id` = ?, `author_id` = ?", $iSystemId, $iCmtId, $iAuthorId);
+        if(!$this->query($sQuery))
+            return false;
+
+        return $this->lastId();
+    }
+
+    protected function convertImagesArray($a)
+    {
+        if (!$a || !is_array($a))
+            return array();
+
+        $aFiles = array ();
+        foreach ($a as $aFile)
+            $aFiles[] = $aFile['image_id'];
+        return $aFiles;
+    }
+}
+
+/** @} */

+ 176 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolCmtsReviews.php

@@ -0,0 +1,176 @@
+<?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
+ * @{
+ */
+
+
+
+/**
+ * Reviews for any content
+ */
+class BxDolCmtsReviews extends BxTemplCmts
+{
+    protected $_iMoodMinValue;
+    protected $_iMoodMaxValue;
+    protected $_sMoodLegendId;
+
+    protected $_sRatingLegendId;
+
+    /*
+     * Determines whether an object author is allowed 
+     * to post review to his own content.
+     */
+    protected $_bOaPostAllowed;
+
+    /*
+     * Determines whether an object author is allowed 
+     * to reply to somebody's review.
+     */
+    protected $_bOaReplyAllowed;
+
+    /*
+     * Determines whether a review author is allowed 
+     * to reply to his own review or to somebody's reply 
+     * to his own review.
+     */
+    protected $_bRaReplyAllowed;
+
+    public function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_sType = BX_DOL_CMT_TYPE_REVIEW;
+        $this->_oQuery = new BxDolCmtsReviewsQuery($this);
+
+        $this->_sFormObject = 'sys_review';
+        $this->_sFormDisplayPost = 'sys_review_post';
+        $this->_sFormDisplayEdit = 'sys_review_edit';
+
+        $this->_aT = array(
+            'block_comments_title' => '_cmt_rvw_block_reviews_title',
+            'block_rating_title' => '_cmt_rvw_block_rating_title',
+            'txt_sample_single' => '_cmt_rvw_txt_sample_review_single',
+            'txt_sample_vote_single' => '_cmt_rvw_txt_sample_vote_single',
+            'txt_sample_reaction_single' => '_cmt_rvw_txt_sample_reaction_single',
+            'txt_sample_score_up_single' => '_cmt_rvw_txt_sample_score_up_single',
+            'txt_sample_score_down_single' => '_cmt_rvw_txt_sample_score_down_single',
+            'txt_min_form_placeholder' => '_cmt_rvw_txt_min_form_placeholder'
+        );
+
+        $this->_iMoodMinValue = 1;
+        $this->_iMoodMaxValue = 5;
+        $this->_sMoodLegendId = "cmt-legend-%s-%d-%d";
+
+        $this->_sRatingLegendId = "cmt-legend-%s-%d";
+
+        $this->_bOaPostAllowed = true;
+        $this->_bOaReplyAllowed = true;
+        $this->_bRaReplyAllowed = true;
+    }
+
+    public function isPostAllowed($isPerformAction = false)
+    {
+        $iUsrId = $this->_getAuthorId();
+        $iObjAthrId = $this->getObjectAuthorId();
+
+        if($iUsrId == $iObjAthrId && !$this->_bOaPostAllowed)
+            return false;
+
+        if($this->isReviewed($iUsrId))
+            return false;
+
+        if($this->isPostAllowedCustom($isPerformAction) === false)
+            return false;
+
+        return parent::isPostAllowed($isPerformAction);
+    }
+
+    public function isReplyAllowed ($mixedCmt, $isPerformAction = false)
+    {
+        $iUsrId = $this->_getAuthorId();
+        $iObjAthrId = $this->getObjectAuthorId();
+        $iRvwAthrId = $this->getReviewAuthorId($mixedCmt);
+
+        if(!in_array($iUsrId, array($iObjAthrId, $iRvwAthrId)))
+            return false;
+
+        if($iUsrId == $iObjAthrId && $iUsrId != $iRvwAthrId && !$this->_bOaReplyAllowed)
+            return false;
+
+        if($iUsrId == $iRvwAthrId && !$this->_bRaReplyAllowed)
+            return false;
+
+        return parent::isPostAllowed($isPerformAction);
+    }
+
+    /**
+     * Can be overwritten if some custom check is needed.
+     * @param boolean $isPerformAction
+     * @return boolean
+     */
+    public function isPostAllowedCustom($isPerformAction = false)
+    {
+        return true;
+    }
+
+    public function isReviewed($mixedCmt)
+    {
+        return $this->_oQuery->isReviewed($this->getId(), $mixedCmt);
+    }
+
+    public function getMoodMinValue()
+    {
+        return $this->_iMoodMinValue;
+    }
+
+    public function getMoodMaxValue()
+    {
+        return $this->_iMoodMaxValue;
+    }
+
+    public function getMoodLegendId($iItemId)
+    {
+        return sprintf($this->_sMoodLegendId, str_replace('_', '-', $this->getSystemName()), $this->getId(), $iItemId);
+    }
+
+    public function getRatingLegendId()
+    {
+        return sprintf($this->_sRatingLegendId, str_replace('_', '-', $this->getSystemName()), $this->getId());
+    }
+
+    public function getReviewAuthorId($mixedCmt)
+    {
+        return $this->_oQuery->getReviewAuthorId($this->getId(), $mixedCmt);
+    }
+
+    public function onEditAfter($iCmtId)
+    {
+        $mixedResult = parent::onEditAfter($iCmtId);
+        if($mixedResult === false)
+            return $mixedResult;
+
+        $aCmt = $this->getCommentSimple($iCmtId);
+
+        return array_merge($mixedResult, array(
+            'mood' => (int)$aCmt['cmt_mood'],
+            'mood_legend_id' => $this->getMoodLegendId($iCmtId)
+        ));
+    }
+
+    protected function _triggerComment()
+    {
+        $mixedResult = parent::_triggerComment();
+        if($mixedResult === false)
+            return $mixedResult;
+
+        $iId = $this->getId();
+        $aStats = $this->_oQuery->getReviewsStats($iId);
+        return $this->_oQuery->updateTriggerTableAvg($iId, (float)$aStats['avg']);
+    }
+}
+
+/** @} */

+ 317 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolConnectionQuery.php

@@ -0,0 +1,317 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Database queries for Connection objects.
+ * @see BxDolConnection
+ */
+class BxDolConnectionQuery extends BxDolDb
+{
+    protected $_aObject;
+    protected $_sTable;
+    protected $_sType;
+
+    public function __construct($aObject)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+        $this->_sTable = $aObject['table'];
+        $this->_sType = $aObject['type'];
+    }
+
+    static public function getConnectionObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_connection` WHERE `object` = ?", $sObject);
+        $aObject = $oDb->getRow($sQuery);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        return $aObject;
+    }
+
+    public function getCommonContentSQLParts ($sContentTable, $sContentField, $iInitiator1, $iInitiator2, $isMutual = false)
+    {
+        $sWhereJoin1 = $this->prepareAsString(" AND `c`.`initiator` = ?", $iInitiator1);
+        $sWhereJoin2 = $this->prepareAsString(" AND `c2`.`initiator` = ?", $iInitiator2);
+        if (false !== $isMutual) {
+            $sWhereJoin1 .= $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual);
+            $sWhereJoin2 .= $this->prepareAsString(" AND `c2`.`mutual` = ?", $isMutual);
+        }
+        return array(
+            'join' => "
+                INNER JOIN `{$this->_sTable}` AS `c` ON (`c`.`content` = `$sContentTable`.`$sContentField` $sWhereJoin1)
+                INNER JOIN `{$this->_sTable}` AS `c2` ON (`c2`.`content` = `c`.`content` $sWhereJoin2)",
+        );
+    }
+
+    public function getConnectedContentSQLParts ($sContentTable, $sContentField, $iInitiator, $isMutual = false)
+    {
+        $aResult = $this->getConnectedContentSQLPartsExt($sContentTable, $sContentField, $iInitiator, $isMutual);
+
+        $aFields = array();
+        foreach($aResult['fields'] as $sFieldAlias => $aField)
+            $aFields[$sFieldAlias] = "`" . $aField['table_alias'] . "`.`" . $aField['name'] . "`";
+
+        return array(
+            'fields' => $aFields,
+            'join' => $aResult['join']['type'] . " JOIN `" . $aResult['join']['table'] . "` AS `" . $aResult['join']['table_alias'] . "` ON (" . $aResult['join']['condition'] . ")",
+        );
+    }
+
+    public function getConnectedContentSQLPartsExt ($sContentTable, $sContentField, $iInitiator, $isMutual = false)
+    {
+        $sWhere = $this->prepareAsString(" AND `c`.`initiator` = ?", $iInitiator);
+        if(false !== $isMutual)
+            $sWhere .= $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual);
+
+        return array(
+            'fields' => array(
+                'added' => array('table_alias' => 'c', 'name' => 'added')
+            ),
+            'join' => array(
+                'type' => 'INNER',
+                'table' => $this->_sTable,
+                'table_alias' => 'c',
+            	'condition' => "`c`.`content` = `" . $sContentTable . "`.`" . $sContentField . "`" . $sWhere
+            )
+        );
+    }
+
+    public function getConnectedContentSQLPartsMultiple ($sContentTable, $sContentField, $sInitiatorTable, $sInitiatorField, $isMutual = false)
+    {
+        $sOn = "";
+        if (false !== $isMutual)
+            $sOn .= $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual);
+        return array(
+            'join' => "INNER JOIN `{$this->_sTable}` AS `c` ON (`c`.`content` = `$sContentTable`.`$sContentField` $sOn)",
+        	'where' => " AND `c`.`initiator` = `$sInitiatorTable`.`$sInitiatorField`"
+        );
+    }
+
+    public function getConnectedInitiatorsSQLParts ($sInitiatorTable, $sInitiatorField, $iContent, $isMutual = false)
+    {
+        $aResult = $this->getConnectedInitiatorsSQLPartsExt($sInitiatorTable, $sInitiatorField, $iContent, $isMutual);
+        return array(
+            'join' => $aResult['join']['type'] . " JOIN `" . $aResult['join']['table'] . "` AS `" . $aResult['join']['table_alias'] . "` ON (" . $aResult['join']['condition'] . ")",
+        );
+    }
+
+    public function getConnectedInitiatorsSQLPartsExt ($sInitiatorTable, $sInitiatorField, $iContent, $isMutual = false)
+    {
+        $sWhere = $this->prepareAsString(" AND `c`.`content` = ?", $iContent);
+        if(false !== $isMutual)
+            $sWhere .= $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual);
+
+        return array(
+            'join' => array(
+                'type' => 'INNER',
+        		'table' => $this->_sTable,
+                'table_alias' => 'c',
+        		'condition' => "`c`.`initiator` = `" . $sInitiatorTable . "`.`" . $sInitiatorField . "`" . $sWhere
+            )
+        );
+    }
+
+    public function getConnectedInitiatorsSQLPartsMultiple ($sInitiatorTable, $sInitiatorField, $sContentTable, $sContentField, $isMutual = false)
+    {
+        $sOn = "";
+        if(false !== $isMutual)
+            $sOn .= $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual);
+
+        return array(
+            'join' => "INNER JOIN `{$this->_sTable}` AS `c` ON (`c`.`initiator` = `$sInitiatorTable`.`$sInitiatorField` $sOn)",
+            'where' => " AND `c`.`content` = `$sContentTable`.`$sContentField`"
+        );
+    }
+
+    public function getCommonContent($iInitiator1, $iInitiator2, $isMutual, $iStart, $iLimit, $iOrder)
+    {
+        $sWhereJoin = (false !== $isMutual) ? " AND `c2`.`mutual` = :mutual" : "";
+        $sJoin = "INNER JOIN `" . $this->_sTable . "` AS `c2` ON (`c2`.`initiator` = :initiator2 AND `c`.`content` = `c2`.`content` $sWhereJoin)";
+
+        $sWhere = " AND `c`.`initiator` = :initiator1";
+        $sQuery = $this->_getConnectionsQuery($sWhere, $sJoin, '`c`.`content`', $isMutual, $iStart, $iLimit, $iOrder);
+
+        return $this->getColumn($sQuery, array(
+    		'mutual' => $isMutual,
+    		'initiator1' => $iInitiator1,
+    		'initiator2' => $iInitiator2,  
+    	));
+    }
+
+    public function getConnectedContent ($iInitiator, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        $sWhere = " AND `c`.`initiator` = :initiator";
+        $sQuery = $this->_getConnectionsQuery($sWhere, '', '`c`.`content`', $isMutual, $iStart, $iLimit, $iOrder);
+
+        return $this->getColumn($sQuery, array(
+        	'initiator' => $iInitiator
+        ));
+    }
+
+    public function getConnectedInitiators ($iContent, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        $sWhere = " AND `c`.`content` = :content";
+        $sQuery = $this->_getConnectionsQuery($sWhere, '', '`c`.`initiator`', $isMutual, $iStart, $iLimit, $iOrder);
+
+        return $this->getColumn($sQuery, array(
+        	'content' => $iContent
+        ));
+    }
+
+    protected function _getConnectionsQuery ($sWhere, $sJoin = '', $sFields = '*', $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        $sOrder = $this->_getOrderClause($iOrder);
+
+        $sWhere .= (false !== $isMutual) ? $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual) : '';
+
+        $sLimit = "";
+        if($iLimit != BX_CONNECTIONS_LIST_NO_LIMIT)
+            $sLimit = $this->prepareAsString("LIMIT ?, ?", $iStart, $iLimit);
+
+        return "SELECT $sFields FROM `" . $this->_sTable . "` AS `c` $sJoin WHERE 1 $sWhere $sOrder $sLimit";
+    }
+
+    public function getConnectedContentCount ($iInitiator, $isMutual = false)
+    {
+        $sWhere = $this->prepareAsString(" AND `c`.`initiator` = ?", $iInitiator);
+        $sQuery = $this->_getConnectionsQueryCount($sWhere, '', $isMutual);
+        return $this->getOne($sQuery);
+    }
+
+    public function getConnectedInitiatorsCount ($iContent, $isMutual = false)
+    {
+        $sWhere = $this->prepareAsString(" AND `c`.`content` = ?", $iContent);
+        $sQuery = $this->_getConnectionsQueryCount($sWhere, '', $isMutual);
+        return $this->getOne($sQuery);
+    }
+
+    protected function _getConnectionsQueryCount ($sWhere, $sJoin = '', $isMutual = false)
+    {
+        $sWhere .= (false !== $isMutual) ? $this->prepareAsString(" AND `c`.`mutual` = ?", $isMutual) : '';
+        return "SELECT COUNT(`id`) FROM `" . $this->_sTable . "` AS `c` $sJoin WHERE 1 $sWhere";
+    }
+
+    protected function _getOrderClause ($iOrder = BX_CONNECTIONS_ORDER_NONE, $sTable = '')
+    {
+        if ($sTable)
+            $sTable .= '.';
+        $sOrder = '';
+        switch ($iOrder) {
+        case BX_CONNECTIONS_ORDER_ADDED_ASC:
+            $sOrder = "ORDER BY {$sTable}`added` ASC";
+            break;
+        case BX_CONNECTIONS_ORDER_ADDED_DESC:
+            $sOrder = "ORDER BY {$sTable}`added` DESC";
+            break;
+        }
+        return $sOrder;
+    }
+
+    public function getConnection ($iInitiator, $iContent)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `" . $this->_sTable . "` WHERE `initiator` = ? AND `content` = ?", $iInitiator, $iContent);
+        return $this->fromMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent, 'getRow', $sQuery);
+    }
+
+    public function addConnection ($iInitiator, $iContent, &$iMutualParam = null)
+    {
+        if ($this->getConnection($iInitiator, $iContent)) // connection already exists
+            return false;
+
+		$aBindings = array();
+
+        $iMutual = 0;
+        $sMutualField = '';
+        if (BX_CONNECTIONS_TYPE_MUTUAL == $this->_sType) {
+            $aConnectionMutual = $this->getConnection($iContent, $iInitiator);
+            $iMutual = $aConnectionMutual ? 1 : 0;
+
+            $sMutualField = ", `mutual` = :mutual";
+            $aBindings['mutual'] = $iMutual;
+        }
+
+        $sQuery = $this->prepare("INSERT INTO `" . $this->_sTable . "` SET `initiator` = :initiator, `content` = :content, `added` = :added" . $sMutualField);
+        if (!$this->query($sQuery, array_merge($aBindings, array('initiator' => $iInitiator, 'content' => $iContent, 'added' => time()))))
+            return false;
+
+        if ($iMutual) // in case of mutual connection update 'mutual' field
+            $this->updateConnectionMutual($iContent, $iInitiator, $iMutual);
+
+        if (null !== $iMutualParam)
+            $iMutualParam = $iMutual;
+
+        $this->cleanMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent);
+            
+        return true;
+    }
+
+    public function updateConnection ($iInitiator, $iContent, $aSet)
+    {
+        if(empty($aSet) || !is_array($aSet))
+            return false;
+
+        $sQuery = $this->prepare("UPDATE `" . $this->_sTable . "` SET " . $this->arrayToSQL($aSet) . " WHERE `initiator` = ? AND `content` = ?", $iInitiator, $iContent);
+        if ($bResult = $this->query($sQuery))
+            $this->cleanMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent);
+
+        return $bResult;
+    }
+
+    public function updateConnectionMutual ($iInitiator, $iContent, $iMutual)
+    {
+        $sQuery = $this->prepare("UPDATE `" . $this->_sTable . "` SET `mutual` = ? WHERE `initiator` = ? AND `content` = ?", $iMutual, $iInitiator, $iContent);
+        if ($bResult = $this->query($sQuery))
+            $this->cleanMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent);
+        return $bResult;
+    }
+
+    public function removeConnection ($iInitiator, $iContent)
+    {
+        if (!($aConnection = $this->getConnection($iInitiator, $iContent))) // connection doesn't exist
+            return true;
+
+        $sQuery = $this->prepare("DELETE FROM `" . $this->_sTable . "` WHERE `initiator` = ? AND `content` = ?", $iInitiator, $iContent);
+        if (!$this->res($sQuery))
+            return false;
+
+        $this->cleanMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent);
+        
+        if (BX_CONNECTIONS_TYPE_MUTUAL == $this->_sType && $aConnection['mutual'])
+            $this->removeConnection($iContent, $iInitiator);
+
+        return true;
+    }
+
+    public function onDelete ($iId, $sField = 'initiator')
+    {
+        $sQuery = $this->prepare("DELETE FROM `{$this->_sTable}` WHERE `$sField` = ?", $iId);
+        return $this->query($sQuery);
+    }
+
+    public function onModuleDelete ($sTable, $sFieldId, $sField = 'initiator')
+    {
+        return $this->onModuleDeleteCustom ($sTable, $sFieldId, $sField);
+    }
+
+    public function onModuleProfileDelete ($sModuleName, $sField = 'initiator')
+    {
+        return $this->onModuleDeleteCustom ('sys_profiles', 'id', $sField, $this->prepareAsString(" AND `sys_profiles`.`type` = ? ", $sModuleName));
+    }
+
+    protected function onModuleDeleteCustom ($sTable, $sFieldId, $sField = 'initiator', $sWhere = '')
+    {
+        $sQuery = $this->prepare("DELETE `" . $this->_sTable . "` FROM `" . $this->_sTable . "` INNER JOIN `{$sTable}` WHERE `" . $this->_sTable . "`.`$sField` = `{$sTable}`.`{$sFieldId}` " . $sWhere);
+        return $this->query($sQuery);
+    }
+
+}
+
+/** @} */

+ 353 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolProfileQuery.php

@@ -0,0 +1,353 @@
+<?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
+ * @{
+ */
+
+/**
+ * All queries related to profiles
+ */
+class BxDolProfileQuery extends BxDolDb implements iBxDolSingleton
+{
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolProfileQuery();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    /**
+     * Get all account profiles.
+     * @param  string  $iAccountId account id
+     * @return profile array
+     */
+    public function getProfilesByAccount ($iAccountId)
+    {
+        if (!$iAccountId)
+            return array();
+
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+        $aAccountInfo = $oAccountQuery->getInfoById($iAccountId);
+
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `account_id` = ? ORDER BY `id` = ? DESC", $iAccountId, $aAccountInfo['profile_id']);
+        return $this->getAllWithKey($sSql, 'id');
+    }
+
+    /**
+     * Get profile by content id, type and account.
+     * @param  string $iAccountId account id
+     * @return array  if aprofile ids, key is profile id
+     */
+    public function getProfileByContentTypeAccount ($iContentId, $sType, $iAccountId)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `account_id` = ? AND `type` = ? AND `content_id` = ?", $iAccountId, $sType, $iContentId);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * Get profile by content id and type.
+     * @param  string $iAccountId account id
+     * @return array  if aprofile ids, key is profile id
+     */
+    public function getProfileByContentAndType ($iContentId, $sType, $bClearCache = false)
+    {
+        if (!$iContentId)
+            return false;
+
+        $sKey = 'BxDolProfileQuery::getProfileByContentAndType' . $iContentId . $sType;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `content_id` = ? AND `type` = ?", $iContentId, $sType);        
+        $mixedResult = $this->fromMemory($sKey, 'getRow', $sSql);
+        if (!$mixedResult)
+            $this->cleanMemory($sKey);
+        return $mixedResult;
+    }
+
+    public function getConnectedProfilesByType ($aSqlParts, $sType, $iStart, $iLimit)
+    {
+        if(empty($aSqlParts['join']))
+            return array();
+
+        $aBindings = array();
+
+        $sSelectClause = '';
+        if(!empty($aSqlParts['fields']))
+            foreach($aSqlParts['fields'] as $sName => $sField)
+                $sSelectClause .= ', ' . $sField;
+
+        $sWhereClause = '';
+        if(!empty($sType)) {
+            $sWhereClause = ' AND `sys_profiles`.`type`=:type';
+
+            $aBindings['type'] = $sType;
+        }
+
+        $sOrderClause = '';
+        if(!empty($aSqlParts['fields']['added']))
+            $sOrderClause = ' ORDER BY ' . $aSqlParts['fields']['added'] . ' DESC';
+
+        $sLimitClause = '';
+        if(!empty($iLimit))
+            $sLimitClause = $this->prepareAsString(' LIMIT ?, ?', $iStart, $iLimit);
+
+        return $this->getAllWithKey("SELECT `sys_profiles`.*" . $sSelectClause . " FROM `sys_profiles` " . $aSqlParts['join'] . " WHERE 1" . $sWhereClause . $sOrderClause . $sLimitClause, 'id', $aBindings);
+    }
+
+    /**
+     * Insert account and content id association. Also if currect profile id is not defined - it updates current profile id in account.
+     * @param $iAccountId account id
+     * @param $iContentId content id
+     * @param $sStatus profile status
+     * @param $sType profile content type
+     * @return inserted profile's id
+     */
+    public function insertProfile ($iAccountId, $iContentId, $sStatus, $sType = 'system')
+    {
+        $sSql = $this->prepare("INSERT INTO `sys_profiles` SET `account_id` = ?, `type` = ?, `content_id` = ?, `status` = ?", $iAccountId, $sType, $iContentId, $sStatus);
+        if (!$this->query($sSql))
+            return false;
+        $iProfileId = $this->lastId();
+        $this->getCurrentProfileByAccount($iAccountId); // it updates current profile id automatically if it is not defined
+        return $iProfileId;
+    }
+
+    /**
+     * Update profile's status
+     * @param $iProfileId profile id
+     * @param $sStatus profile status
+     * @return true on success or false otherwise
+     */
+    public function changeStatus ($iProfileId, $sStatus)
+    {
+        return $this->_updateField ($iProfileId, 'status', $sStatus);
+    }
+
+    /**
+     * Get current account profile.
+     * @param  string  $iAccountId account id
+     * @return current account's profile id
+     */
+    public function getCurrentProfileByAccount ($iAccountId, $bClearCache = false)
+    {
+        if (!$iAccountId)
+            return false;
+
+        $sKey = 'BxDolProfileQuery::getCurrentProfileByAccount' . $iAccountId;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+
+        $sSql = $this->prepare("SELECT `profile_id` FROM `sys_accounts` WHERE `id` = ? LIMIT 1", $iAccountId);        
+        $iProfileId = $this->fromMemory($sKey, 'getOne', $sSql);
+        if (!$iProfileId) {
+            $this->cleanMemory($sKey);
+            $sSql = $this->prepare("SELECT `id` FROM `sys_profiles` WHERE `account_id` = ? LIMIT 1", $iAccountId);
+            $iProfileId = $this->getOne($sSql);
+            if (!$iProfileId)
+                return false;
+
+            if (!BxDolAccountQuery::getInstance()->updateCurrentProfile($iAccountId, $iProfileId))
+                return false;
+        }
+        return $iProfileId;
+    }
+
+    /**
+     * Get profile by specified field name and value.
+     * It is for internal usage only.
+     * Use other funtions to get profile info, like getInfoById, etc.
+     * @param  string $sField database field name
+     * @param  mixed  $sValue database field value
+     * @return array  with porfile info
+     */
+    protected function _getDataByField ($sField, $sValue)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `$sField` = ? LIMIT 1", $sValue);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * get profile id by id
+     */
+    public function getIdById($iId, $bClearCache = false)
+    {
+        if (!$iId)
+            return false;
+
+        $sKey = 'BxDolProfileQuery::getIdById' . $iId;
+        if ($bClearCache)
+            $this->cleanMemory($sKey);
+        
+        $sSql = $this->prepare("SELECT `id` FROM `sys_profiles` WHERE `id` = ? LIMIT 1", $iId);
+        $mixedResult = $this->fromMemory($sKey, 'getOne', $sSql);
+        if (!$mixedResult)
+            $this->cleanMemory($sKey);
+        return $mixedResult;
+    }
+
+    /**
+     * get account info by profile id
+     */
+    public function getAccountInfoByProfileId($iId)
+    {
+        $sSql = $this->prepare("SELECT `a`.* FROM `sys_accounts` AS `a` INNER JOIN `sys_profiles` AS `p` ON (`p`.`account_id` = `a`.`id`) WHERE `p`.`id` = ? LIMIT 1", $iId);
+        return $this->getRow($sSql);
+    }
+
+    /**
+     * get account email by profile id
+     */
+    public function getEmailById($iId)
+    {
+        $a = $this->getAccountInfoByProfileId($iId);
+        if (!$a || empty($a['email']))
+            return false;
+        return $a['email'];
+    }
+
+    /**
+     * Get profile info by id
+     * @param  int   $iID profile id
+     * @return array with profile info
+     */
+    public function getInfoById( $iID )
+    {
+        return $this->_getDataByField('id', (int)$iID);
+    }
+
+	/**
+     * Is profile online by id
+     * @param  int $iId profile id
+     * @return profile online status
+     */
+    public function isOnline($iId)
+    {
+        $sSql = $this->prepare("SELECT 
+        		`tp`.`id` 
+        	FROM `sys_profiles` AS `tp` 
+        	INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` 
+        	INNER JOIN `sys_sessions` AS `ts` ON `tp`.`account_id`=`ts`.`user_id` 
+        	WHERE 
+        		`tp`.`id` = ? AND 
+        		`ta`.`profile_id`=`tp`.`id` AND 
+        		`ts`.`date` > (UNIX_TIMESTAMP() - 60 * ?) 
+        	LIMIT 1", $iId, (int)getParam('sys_account_online_time'));
+        return (int)$this->getOne($sSql) > 0;
+    }
+    
+    /**
+     * Get online profiles count 
+     * @return online profiles count 
+     */
+    public function getOnlineCount()
+    {
+        $sSql = $this->prepare("SELECT 
+        		COUNT(`tp`.`id`) 
+        	FROM `sys_profiles` AS `tp` 
+        	INNER JOIN `sys_accounts` AS `ta` ON `tp`.`account_id`=`ta`.`id` 
+        	INNER JOIN `sys_sessions` AS `ts` ON `tp`.`account_id`=`ts`.`user_id` 
+        	WHERE 
+        		`ta`.`profile_id`=`tp`.`id` AND 
+        		`ts`.`date` > (UNIX_TIMESTAMP() - 60 * ?)", (int)getParam('sys_account_online_time'));
+        return (int)$this->getOne($sSql);
+    }
+
+    /**
+     * Get profile field by specified field name and value.
+     * In most cases it is for internal usage only.
+     * Use other funtions to get profile info, like getIdByEmail, etc.
+     * @param  string    $sFieldRequested database field name to return
+     * @param  string    $sFieldSearch    database field name to search for
+     * @param  mixed     $sValue          database field value
+     * @return specified profile field value
+     */
+    protected function _getFieldByField ($sFieldRequested, $sFieldSearch, $sValue)
+    {
+        $sSql = $this->prepare("SELECT `$sFieldRequested` FROM `sys_profiles` WHERE `$sFieldSearch` = ? LIMIT 1", $sValue);
+        return $this->getOne($sSql);
+    }
+
+    /**
+     * Update some field by profile id
+     * In most cases it is for internal usage only.
+     * Use other funtions to get profile info, like updateLogged, etc.
+     * @param  string    $sFieldRequested database field name to return
+     * @param  string    $sFieldSearch    database field name to search for
+     * @param  mixed     $sValue          database field value
+     * @return specified profile field value
+     */
+    protected function _updateField ($iId, $sFieldForUpdate, $sValue)
+    {
+        $sSql = $this->prepare("UPDATE `sys_profiles` SET `$sFieldForUpdate` = ? WHERE `id` = ? LIMIT 1", $sValue, $iId);
+        return $this->query($sSql);
+    }
+
+    /**
+     * Delete profile info by id
+     * @param  int      $iID profile id
+     * @return affected rows
+     */
+    public function delete($iID)
+    {
+        $aInfo = $this->getInfoById($iID);
+        $sSql = $this->prepare("DELETE FROM `sys_profiles` WHERE `id` = ? LIMIT 1", $iID);
+        if ($res = $this->query($sSql)) {
+            $this->getProfileByContentAndType($aInfo['content_id'], $aInfo['type'], true);
+            $this->getCurrentProfileByAccount($aInfo['account_id'], true);
+            $this->getIdById($iID, true);
+        }
+        return $res;
+    }
+
+    /**
+     * Reset deleted profile ids and assign system profile ids.
+     * Should be called after profiles module deletion.
+     * It can be called automatically if 
+     * @code
+     * 'process_deleted_profiles' => 1 
+     * @endcode
+     * is specified in module config.php file in 'uninstall' section.
+     */
+    public function processDeletedProfiles ()
+    {
+        $bResult = true;
+
+        // reset deleted profiles
+        $bResult &= $this->query("UPDATE `sys_accounts` AS `a` LEFT OUTER JOIN `sys_profiles` AS `p` ON `a`.`profile_id`=`p`.`id` SET `a`.`profile_id`='0' WHERE `p`.`id` IS NULL") !== false;
+
+        // try to assign another non-system profile to reset accounts
+        $bResult &= $this->query("UPDATE `sys_accounts` AS `a` INNER JOIN `sys_profiles` AS `p` ON `a`.`id`=`p`.`account_id` AND `p`.`type`<>'system' AND `a`.`profile_id`='0' SET `a`.`profile_id`=`p`.`id`") !== false;
+
+        // assign system profile to reset accounts
+        $bResult &= $this->query("UPDATE `sys_accounts` AS `a` INNER JOIN `sys_profiles` AS `p` ON `a`.`id`=`p`.`account_id` AND `p`.`type`='system' AND `a`.`profile_id`='0' SET `a`.`profile_id`=`p`.`id`") !== false;
+
+        return $bResult;
+    }
+}
+
+/** @} */

+ 89 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/classes/BxDolRelationQuery.php

@@ -0,0 +1,89 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Database queries for Relation objects.
+ * @see BxDolConnection
+ */
+class BxDolRelationQuery extends BxDolConnectionQuery
+{
+    public function __construct($aObject)
+    {
+        parent::__construct($aObject);
+    }
+
+    public function addConnection ($iInitiator, $iContent, &$iMutualParam = null)
+    {
+        if ($this->getConnection($iInitiator, $iContent)) // connection already exists
+            return false;
+
+        $iMutual = 0;
+        if(!$this->query("INSERT INTO `" . $this->_sTable . "` SET `initiator` = :initiator, `content` = :content, `added` = :added, `mutual` = :mutual", array(
+            'initiator' => $iInitiator, 
+            'content' => $iContent, 
+            'mutual' => $iMutual, 
+            'added' => time()
+        ))) return false;
+
+        if($iMutualParam !== null)
+            $iMutualParam = $iMutual;
+
+        $this->cleanMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent);
+        return true;
+    }
+
+    public function removeConnection ($iInitiator, $iContent)
+    {
+        $sQuery = $this->prepare("DELETE FROM `" . $this->_sTable . "` WHERE `initiator` = ? AND `content` = ?", $iInitiator, $iContent);
+        if(!$this->res($sQuery))
+            return false;
+
+        $this->cleanMemory('BxDolConnectionQuery::getConnection' . $this->_sTable . $iInitiator . '_' . $iContent);
+        return true;
+    }
+    
+    public function getCommonContentExt($iInitiator1, $iInitiator2, $isMutual, $iStart, $iLimit, $iOrder)
+    {
+        $sFields = "`c`.`content` AS `content`, `c`.`initiator` AS `initiator1`, `c`.`relation` AS `relation1`, `c`.`mutual` AS `mutual1`, `c2`.`initiator` AS `initiator2`, `c2`.`relation` AS `relation2`, `c2`.`mutual` AS `mutual2`";
+
+        $sWhereJoin = (false !== $isMutual) ? " AND `c2`.`mutual` = :mutual" : "";
+        $sJoin = "INNER JOIN `" . $this->_sTable . "` AS `c2` ON (`c2`.`initiator` = :initiator2 AND `c`.`content` = `c2`.`content` $sWhereJoin)";
+
+        $sWhere = " AND `c`.`initiator` = :initiator1";
+        $sQuery = $this->_getConnectionsQuery($sWhere, $sJoin, $sFields, $isMutual, $iStart, $iLimit, $iOrder);
+
+        return $this->getAllWithKey($sQuery, 'content', array(
+            'mutual' => $isMutual,
+            'initiator1' => $iInitiator1,
+            'initiator2' => $iInitiator2
+    	));
+    }
+
+    public function getConnectedContentExt($iInitiator, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        $sWhere = " AND `c`.`initiator` = :initiator";
+        $sQuery = $this->_getConnectionsQuery($sWhere, '', '*', $isMutual, $iStart, $iLimit, $iOrder);
+
+        return $this->getAllWithKey($sQuery, 'content', array(
+            'initiator' => $iInitiator
+        ));
+    }
+
+    public function getConnectedInitiatorsExt($iContent, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        $sWhere = " AND `c`.`content` = :content";
+        $sQuery = $this->_getConnectionsQuery($sWhere, '', '*', $isMutual, $iStart, $iLimit, $iOrder);
+
+        return $this->getAllWithKey($sQuery, 'initiator', array(
+            'content' => $iContent
+        ));
+    }
+}
+
+/** @} */

+ 752 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/js/classes/BxDolCmts.js

@@ -0,0 +1,752 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+function BxDolCmts (options) {
+    this._sObjName = undefined == options.sObjName ? 'oCmts' : options.sObjName;    // javascript object name, to run current object instance from onTimer
+    this._sActionsUrl = options.sRootUrl + 'cmts.php'; // actions url address
+
+    this._sSystem = options.sSystem; // current comment system
+    this._iAuthorId = options.iAuthorId; // this comment's author ID.
+    this._iObjId = options.iObjId; // this object id comments
+    this._sBaseUrl = options.sBaseUrl; // base url to view comment's listing.
+
+    this._iMinPostForm = undefined == options.iMinPostForm ? 0 : options.iMinPostForm;
+    this._sPostFormPosition = undefined == options.sPostFormPosition ? 'top' : options.sPostFormPosition;
+    this._sDisplayType = undefined == options.sDisplayType ? 'threaded' : options.sDisplayType;
+    this._sBrowseType = undefined == options.sBrowseType ? 'tail' : options.sBrowseType;
+    this._sBrowseFilter = undefined == options.sBrowseFilter ? 'all' : options.sBrowseFilter;
+
+    this._sSP = options.sStylePrefix === undefined ? 'cmt' : options.sStylePrefix;
+    this._sAnimationEffect = 'fade';
+    this._iAnimationSpeed = 'slow';
+
+    this._oSavedTexts = {};
+    this._sRootId = '#cmts-box-' + this._sSystem + '-' + this._iObjId;
+
+    this._bLiveUpdatePaused = false;
+}
+
+/*--- Main layout functionality ---*/
+BxDolCmts.prototype.cmtInit = function()
+{
+    var $this = this;
+
+    $(document).ready(function() {
+        // init post comment form    
+        var sFormId = $this._sRootId + ' .cmt-post-reply form';
+        if ($(sFormId).length) {
+            $(sFormId).each(function() {
+                $this.cmtInitFormPost($(this));
+            });
+        }
+
+        // blink (highlight) necessary comments
+        $this._cmtsBlink($($this._sRootId));
+    });
+};
+
+BxDolCmts.prototype.cmtInitFormPost = function(oCmtForm)
+{
+    var $this = this;
+
+    oCmtForm.ajaxForm({
+        dataType: "json",
+        beforeSubmit: function (formData, jqForm, options) {
+            window[$this._sObjName].cmtBeforePostSubmit(oCmtForm);
+        },
+        success: function (oData) {
+            window[$this._sObjName].cmtAfterPostSubmit(oCmtForm, oData);
+        }
+    });
+};
+
+BxDolCmts.prototype.cmtShowForm = function(oElement)
+{
+    var oForm = $(oElement).parents('.cmt-reply.cmt-reply-min:first');
+    oForm.find('.cmt-body-min').bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function() {
+        oForm.find('.cmt-body').show(function() {
+            var oTextarea = oForm.find("[name='cmt_text']");
+            if(typeof bx_editor_get_htmleditable === 'function')
+                oTextarea = bx_editor_get_htmleditable(oTextarea);
+
+            if(!oTextarea || oTextarea.length == 0)
+                return;
+
+            oTextarea.focus();
+        });
+    });
+};
+
+BxDolCmts.prototype.cmtBeforePostSubmit = function(oCmtForm)
+{
+    this._loadingInButton($(oCmtForm).children().find(':submit'), true);
+};
+
+BxDolCmts.prototype.cmtAfterPostSubmit = function (oCmtForm, oData)
+{
+	var $this = this;
+	var fContinue = function() {
+            var oParent = oCmtForm.parents('.cmt-reply:first');
+
+            if(oData && oData.id != undefined) {
+                var iCmtId = parseInt(oData.id);
+                if(iCmtId > 0) {
+                    $this._getCmt(oCmtForm, iCmtId);
+
+                    var iCmtParentId = parseInt(oData.parent_id);
+                    if(iCmtParentId == 0)
+                        $this._getForm(undefined, {CmtParent: iCmtParentId}, function(sFormWrp) {
+                            if(sFormWrp && sFormWrp.length > 0)
+                                sFormWrp = $(sFormWrp).html();
+
+                            oParent.hide().html(sFormWrp).show();
+
+                            $this.cmtInitFormPost(oParent.find('form'));
+                        });
+                    else
+                        oParent.remove();
+                        
+                }
+
+                return;
+            }
+
+            if(oData && oData.form != undefined && oData.form_id != undefined) {
+                oParent.find('form').replaceWith(oData.form);
+
+                $this.cmtInitFormPost(oParent.find('form'));
+
+                return;
+            }
+	};
+
+	this._loadingInButton($(oCmtForm).children().find(':submit'), false);
+
+	if(oData && oData.msg != undefined)
+            bx_alert(oData.msg, fContinue);
+        else 
+            fContinue();
+};
+
+BxDolCmts.prototype.cmtInitFormEdit = function(sCmtFormId)
+{
+	var $this = this;
+	var oCmtForm = $('#' + sCmtFormId);
+
+	oCmtForm.ajaxForm({
+        dataType: "json",
+        beforeSubmit: function (formData, jqForm, options) {
+        	window[$this._sObjName].cmtBeforeEditSubmit(oCmtForm);
+        },
+        success: function (oData) {
+        	window[$this._sObjName].cmtAfterEditSubmit(oCmtForm, oData);
+        }
+    });
+};
+
+BxDolCmts.prototype.cmtBeforeEditSubmit = function(oCmtForm)
+{
+	this._loadingInButton($(oCmtForm).children().find(':submit'), true);
+};
+
+BxDolCmts.prototype.cmtAfterEditSubmit = function (oCmtForm, oData, onComplete)
+{
+    var $this = this;
+    var fContinue = function() {
+        if(oData && oData.id != undefined && oData.text != undefined) {
+            var iCmtId = parseInt(oData.id);
+            if(iCmtId > 0) {
+                $('#cmt' + iCmtId + ' .cmt-body').bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+                    $(this).html(oData.text).bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+                });
+            }
+        }
+
+        if(oData && oData.form != undefined && oData.form_id != undefined) {
+            $('#' + oData.form_id).replaceWith(oData.form);
+            $this.cmtInitFormEdit(oData.form_id);
+        }
+
+        if(typeof onComplete == 'function')
+            onComplete(oCmtForm, oData);
+    };
+
+    this._loadingInButton($(oCmtForm).children().find(':submit'), false);
+
+    if(oData && oData.msg != undefined)
+        bx_alert(oData.msg, fContinue);
+    else 
+            fContinue();
+};
+
+BxDolCmts.prototype.cmtEdit = function(oLink, iCmtId) {
+    var $this = this;
+    var oParams = this._getDefaultActions();
+    oParams['action'] = 'GetFormEdit';
+    oParams['Cmt'] = iCmtId;
+
+    $(oLink).parents('.bx-popup-applied:first:visible').dolPopupHide();
+
+    var sBodyId = this._sRootId + ' #cmt' + iCmtId + ' .cmt-body:first';
+    if ($(sBodyId + ' > form').length) {
+        $(sBodyId).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+            $(this).html($this._oSavedTexts[iCmtId]).bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+        });
+        return;
+    }
+    else
+        this._oSavedTexts[iCmtId] = $(sBodyId).html();
+
+    this._loadingInContent (oLink, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oParams,
+        function (oData) {
+        	var fContinue = function() {
+        		if(oData && oData.form != undefined && oData.form_id != undefined) {
+            		$(sBodyId).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+                        $(this).html(oData.form).bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+                        	$this.cmtInitFormEdit(oData.form_id);
+                        });
+                    });
+            	}
+        	};
+
+        	$this._loadingInContent (oLink, false);
+
+        	if(oData && oData.msg != undefined)
+                bx_alert(oData.msg, fContinue);
+        	else
+        		fContinue();
+        },
+        'json'
+    );
+};
+
+BxDolCmts.prototype.cmtRemove = function(e, iCmtId) {
+	var $this = this;
+
+	$(e).parents('.bx-popup-active:first').dolPopupHide();
+
+	bx_confirm('', function() {
+	    var oParams = $this._getDefaultActions();
+	    oParams['action'] = 'Remove';
+	    oParams['Cmt'] = iCmtId;
+
+	    $this._loadingInContent(e, true);
+
+	    jQuery.post (
+	        $this._sActionsUrl,
+	        oParams,
+	        function(oData) {
+	            var fContinue = function() {
+	            	if(oData && oData.id != undefined) {
+		            	$(e).parents('.bx-popup-applied:first:visible').dolPopupHide();
+
+		            	$('#cmt' + oData.id).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+		                	var oCounter = $(this).parent('ul.cmts').siblings('.cmt-cont').find('.cmt-actions a.cmt-comment-replies span');
+		                	if(oCounter)
+		                		oCounter.html(oCounter.html() - 1);
+
+		                	$(this).remove();
+		                });
+		            }
+	            };
+
+	            $this._loadingInContent(e, false);
+
+	            if(oData && oData.msg != undefined)
+	                bx_alert(oData.msg, fContinue);	            
+	            else
+	            	fContinue();
+	        },
+	        'json'
+	    );
+	});
+};
+
+BxDolCmts.prototype.cmtLoad = function(oLink, iCmtParentId, iStart, iPerView)
+{
+	var $this = this;
+	var bButton = $(oLink).hasClass('bx-btn');
+
+	if(bButton)
+		this._loadingInButton(oLink, true);
+	else 
+		this._loading($(oLink).parents('ul.cmts:first'), true);
+
+	this._getCmts(oLink, {
+		CmtParent: iCmtParentId,
+		CmtStart: iStart,
+		CmtPerView: iPerView,
+		CmtBrowse: this._sBrowseType,
+		CmtFilter: this._sBrowseFilter,
+		CmtDisplay: this._sDisplayType
+	}, function(sListId, sContent) {
+		if(bButton)
+			$this._loadingInButton(oLink, false);
+		else 
+			$this._loading($(oLink).parents('ul.cmts:first'), false);
+
+		$this._cmtsReplace($(oLink).parents('li:first'), sContent);
+	});
+};
+
+BxDolCmts.prototype.cmtChangeDisplay = function(oLink, sType)
+{
+	var $this = this;
+	this._getCmts(oLink, {
+		CmtParent: 0,
+		CmtBrowse: this._sBrowseType,
+		CmtFilter: this._sBrowseFilter,
+		CmtDisplay: sType
+	}, function(sListId, sContent) {
+		$this._sDisplayType = sType;
+		$this._cmtsReplaceContent($(sListId), sContent);
+	});
+};
+
+BxDolCmts.prototype.cmtChangeBrowse = function(oLink, sType)
+{
+	var $this = this;
+	this._getCmts(oLink, {
+		CmtParent: 0,
+		CmtBrowse: sType,
+		CmtFilter: this._sBrowseFilter,
+		CmtDisplay: this._sDisplayType
+	}, function(sListId, sContent) {
+		$this._sBrowseType = sType;
+		$this._cmtsReplaceContent($(sListId), sContent);
+	});
+};
+
+BxDolCmts.prototype.cmtChangeFilter = function(oLink, sType)
+{
+	var $this = this;
+	this._getCmts(oLink, {
+		CmtParent: 0,
+		CmtBrowse: this._sBrowseType,
+		CmtFilter: sType,
+		CmtDisplay: this._sDisplayType
+	}, function(sListId, sContent) {
+		$this._sBrowseFilter = sType;
+		$this._cmtsReplaceContent($(sListId), sContent);
+	});
+};
+
+BxDolCmts.prototype.showReplacement = function(iCmtId)
+{
+    $('#cmt' + iCmtId + '-hidden').bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function(){
+        $(this).next('#cmt' + iCmtId).bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+    });
+};
+
+BxDolCmts.prototype.showMore = function(oLink)
+{
+	$(oLink).parent('span').next('span').show().prev('span').remove();
+};
+
+BxDolCmts.prototype.showImage = function(oLink, sUrl) {
+	$(this._sRootId + '-view-image-popup').dolPopupImage(sUrl, $(oLink).parents('.cmt-attached:first'));
+};
+
+BxDolCmts.prototype.toggleReply = function(e, iCmtParentId, iQuote)
+{
+    var $this = this;
+    var aParams = {
+        CmtParent: iCmtParentId,
+        CmtQuote: iQuote != undefined ? parseInt(iQuote) : 0
+    };
+    var fOnShow = function() {
+        $(this).find('textarea:first').focus();
+    };
+
+    var sParentId = this._sRootId + ' #cmt' + iCmtParentId;
+    var sReplyQuoteId = '.cmt-reply-quote';
+    if(!aParams['CmtQuote'])
+        sReplyQuoteId = ':not(' + sReplyQuoteId + ')';
+    var sReplyId = '.cmt-reply' + sReplyQuoteId;
+
+    if ($(sParentId + ' > ' + sReplyId).length) {
+        $(sParentId + ' > ' + sReplyId).bx_anim('toggle', this._sAnimationEffect, this._iAnimationSpeed, fOnShow);
+        return;
+    }
+
+    this._getForm(e, aParams, function(sForm) {
+        var oForm = $(sForm).hide().addClass('cmt-reply-' + $this._sPostFormPosition).addClass('cmt-reply-margin');
+        var oFormSibling = $(sParentId + ' > ul.cmts:first');
+        switch($this._sPostFormPosition) {
+            case 'top':
+                oFormSibling.before(oForm);
+                break;
+
+            case 'bottom':
+                oFormSibling.after(oForm);
+                break;
+        }
+
+        $this.cmtInitFormPost(oForm.find('form'));
+
+        $(sParentId).children(sReplyId).bx_anim('toggle', $this._sAnimationEffect, $this._iAnimationSpeed, fOnShow);
+    });
+};
+
+BxDolCmts.prototype.toggleQuote = function(e, iCmtParentId)
+{
+    this.toggleReply(e, iCmtParentId, 1);
+};
+
+BxDolCmts.prototype.goTo = function(oLink, sGoToId, sBlinkIds, onLoad)
+{
+    $(oLink).parents('.bx-popup-applied:first:visible').dolPopupHide();
+
+    var $this = this;
+    this._getCmts(null, {
+        CmtParent: 0,
+        CmtBrowse: this._sBrowseType,
+        CmtFilter: this._sBrowseFilter,
+        CmtDisplay: this._sDisplayType,
+        CmtBlink: sBlinkIds
+    }, function(sListId, sContent) {
+        $this._cmtsReplaceContent($(sListId), sContent, function() {
+            location.hash = sGoToId;
+
+            if(typeof onLoad == 'function')
+                onLoad();
+        });
+    });
+};
+
+BxDolCmts.prototype.goToBtn = function(oLink, sGoToId, sBlinkIds, onLoad)
+{
+    var $this = this;
+
+    this._loadingInButton(oLink, true);
+
+    this._getCmts(null, {
+        CmtParent: 0,
+        CmtBrowse: this._sBrowseType,
+        CmtFilter: this._sBrowseFilter,
+        CmtDisplay: this._sDisplayType,
+        CmtBlink: sBlinkIds
+    }, function(sListId, sContent) {
+        $this._cmtsReplaceContent($(sListId), sContent, function() {
+            location.hash = sGoToId;
+
+            if(typeof onLoad == 'function')
+                onLoad();
+
+            $this._loadingInButton(oLink, false);
+            $(oLink).parents('.cmt-lu-button:first').remove();
+
+            $this.resumeLiveUpdates();
+        });
+    });
+};
+
+BxDolCmts.prototype.goToAndReply = function(oLink, sGoToId, sBlinkIds)
+{
+    var $this = this;
+    this.goTo(oLink, sGoToId, sBlinkIds, function() {
+        var aBlinkIds = sBlinkIds.split(",");
+        $.each(aBlinkIds, function(iIndex, iValue) {
+            $this.toggleReply(null, iValue);
+        });
+    });
+};
+
+/*----------------------------*/
+/*--- Live Updates methods ---*/
+/*----------------------------*/
+BxDolCmts.prototype.showLiveUpdate = function(oData)
+{
+    if(!oData.code)
+        return;
+
+    var oButton = $(oData.code);
+    var sId = oButton.attr('id');
+    $('#' + sId).remove();
+
+    oButton.prependTo(this._sRootId);
+};
+
+BxDolCmts.prototype.showLiveUpdates = function(oData)
+{
+	/*
+	 * Note. oData.count_old and oData.count_new are also available and can be checked or used in notification popup.  
+	 */
+	if(!oData.code)
+            return;
+
+	var $this = this;
+
+	var oNotifs = $(oData.code);
+	var sId = oNotifs.attr('id');
+	$('#' + sId).remove();
+
+	oNotifs.prependTo('body').dolPopup({
+    	position: 'fixed',
+    	left: '1rem',
+    	top: 'auto',
+    	bottom: '1rem',
+    	fog: false,
+    	onBeforeShow: function() {
+    	},
+    	onBeforeHide: function() {
+    	},
+        onShow: function() {
+            setTimeout(function() {
+                $('.bx-popup-chain.bx-popup-applied:visible:first').dolPopupHide();
+            }, 5000);
+        },
+    	onHide: function() {
+            $this.resumeLiveUpdates();
+    	}
+    });
+};
+
+BxDolCmts.prototype.previousLiveUpdate = function(oLink)
+{
+	var fPrevious = function() {
+		var sClass = 'bx-popup-chain-item';
+		$(oLink).parents('.' + sClass + ':first').hide().prev('.' + sClass).show();
+	};
+
+	if(!this.pauseLiveUpdates(fPrevious));
+		fPrevious();
+};
+
+BxDolCmts.prototype.hideLiveUpdate = function(oLink)
+{
+	$(oLink).parents('.bx-popup-applied:visible:first').dolPopupHide();
+};
+
+BxDolCmts.prototype.resumeLiveUpdates = function(onLoad)
+{
+	if(!this._bLiveUpdatePaused)
+		return false;
+
+	var $this = this;
+	this.changeLiveUpdates('ResumeLiveUpdate', function() {
+		$this._bLiveUpdatePaused = false;
+
+		if(typeof onLoad == 'function')
+			onLoad();
+	});
+
+	return true;
+};
+
+BxDolCmts.prototype.pauseLiveUpdates = function(onLoad)
+{
+	if(this._bLiveUpdatePaused)
+		return false;
+
+	var $this = this;
+	this.changeLiveUpdates('PauseLiveUpdate', function() {
+		$this._bLiveUpdatePaused = true;
+
+		if(typeof onLoad == 'function')
+			onLoad();
+	});
+
+	return true;
+};
+
+BxDolCmts.prototype.changeLiveUpdates = function(sAction, onLoad)
+{
+	var $this = this;
+    var oParams = this._getDefaultActions();
+    oParams['action'] = sAction;
+
+	jQuery.get(
+	    this._sActionsUrl,
+	    oParams,
+	    function() {
+	    	if(typeof onLoad == 'function')
+				onLoad();
+	    }
+	);
+};
+
+/*----------------------------------*/
+/*--- Methods for internal usage ---*/
+/*----------------------------------*/
+BxDolCmts.prototype._getCmt = function (e, iCmtId)
+{
+    var $this = this;
+    var oData = this._getDefaultActions();
+    oData['action'] = 'GetCmt';
+    oData['Cmt'] = iCmtId;
+    oData['CmtBrowse'] = this._sBrowseType;
+    oData['CmtDisplay'] = this._sDisplayType;
+
+    this._loadingInBlock (e);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oData,
+        function (oData) {
+            $this._loadingInBlock (e, false);
+
+            var sListId = $this._sRootId + ' #cmt' + oData.vparent_id + ' > ul';
+            var sReplyFormId = $this._sRootId + ' #cmt' + oData.parent_id + ' > .cmt-reply';
+
+            //--- Hide reply form ---//
+            if($(sReplyFormId).length)
+            	$(sReplyFormId).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed);
+
+            $(sListId).each(function() {
+                //--- Some number of comments already loaded ---//
+                if($(this).children('li.cmt').length)
+                    $(this).children('li.cmt:last').after($(oData.content).hide()).next('li.cmt:hidden').bxProcessHtml().bx_anim('toggle', $this._sAnimationEffect, $this._iAnimationSpeed);
+                //-- There is no comments at all ---//
+                else
+                    $(this).hide().html(oData.content).bxProcessHtml().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+            });
+        },
+        'json'
+    );
+};
+
+BxDolCmts.prototype._getCmts = function (oElement, oRequestParams, onLoad)
+{
+    var $this = this;
+    var oData = this._getDefaultActions();    
+    oData['action'] = 'GetCmts';
+    oData = $.extend({}, oData, oRequestParams);
+
+    var sListId =  this._sRootId + ' #cmt' + oData['CmtParent'] + ' > ul:first';
+
+    if(oElement)
+        this._loadingInBlock(oElement, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oData,
+        function(s) {
+        	if(oElement)
+        		$this._loadingInBlock(oElement, false);
+
+        	if(typeof onLoad == 'function')
+    			onLoad(sListId, s);
+        	else
+        		$this._cmtsAppend(sListId, s);
+        }
+    );
+};
+
+BxDolCmts.prototype._getForm = function (e, oParams, onLoad)
+{
+    var $this = this;
+    var oData = $.extend({}, this._getDefaultActions(), {
+        action: 'GetFormPost',
+        CmtType: 'reply',
+        CmtBrowse: this._sBrowseType, 
+        CmtDisplay: this._sDisplayType, 
+        CmtMinPostForm: this._iMinPostForm
+    }, oParams);
+
+    if(e)
+    	this._loadingInContent(e, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oData,
+        function (s) {
+            if(e)
+                $this._loadingInContent(e, false);
+
+            if(typeof onLoad == 'function')
+                onLoad(s);
+        }
+    );
+};
+
+BxDolCmts.prototype._cmtsAppend = function(sIdTo, sContent)
+{
+	$(sIdTo).append($(sContent).hide()).children(':hidden').bxProcessHtml().bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+};
+
+BxDolCmts.prototype._cmtsPrepend = function(sIdTo, sContent)
+{
+	$(sIdTo).prepend($(sContent).hide()).children(':hidden').bxProcessHtml().bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+};
+
+BxDolCmts.prototype._cmtsReplace = function(oReplace, sContent, onLoad)
+{
+	var $this = this;
+	$(oReplace).bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function() {
+		$(this).after($(sContent).hide()).nextAll(':hidden').bxProcessHtml().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+			$this._cmtsBlink($(this));
+
+			if(typeof onLoad == 'function')
+    			onLoad();
+		});
+		$(this).remove();
+	});
+};
+BxDolCmts.prototype._cmtsReplaceContent = function(oParent, sContent, onLoad)
+{
+	var $this = this;
+	$(oParent).bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function() {
+		$(this).html(sContent).bxProcessHtml().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+			$this._cmtsBlink($(this));
+
+			if(typeof onLoad == 'function')
+    			onLoad();
+		});
+	});
+};
+
+BxDolCmts.prototype._cmtsBlink = function(oParent)
+{
+	var sBlinkClass = 'cmt-blink';
+
+	oParent.find('.' + sBlinkClass + '-plate:visible').animate({
+		opacity: 0
+	}, 
+	5000, 
+	function() {
+		oParent.find('.' + sBlinkClass).removeClass(sBlinkClass);
+	});
+};
+
+BxDolCmts.prototype._getDefaultActions = function() {
+    var oDate = new Date();
+    return {
+        sys: this._sSystem,
+        id: this._iObjId,
+        _t: oDate.getTime()
+    };
+};
+
+BxDolCmts.prototype._loading = function(e, bShow) {
+    var oParent = $(e).length ? $(e) : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxDolCmts.prototype._loadingInBlock = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-db-content:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxDolCmts.prototype._loadingInContent = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('li.cmt:first,.cmt-reply:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxDolCmts.prototype._loadingInButton = function(e, bShow) {
+    if($(e).length)
+        bx_loading_btn($(e), bShow);
+    else
+        bx_loading($('body'), bShow);
+};
+
+/** @} */

+ 72 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/js/classes/BxDolPush.js

@@ -0,0 +1,72 @@
+function BxDolPush(oOptions) {
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxDolPush' : oOptions.sObjName;
+    this._sSiteName = oOptions.sSiteName == undefined ? '' : oOptions.sSiteName;
+    this._iProfileId = oOptions.iProfileId == undefined ? 0 : oOptions.iProfileId;
+    this._sAppId = oOptions.sAppId == undefined ? '' : oOptions.sAppId;
+    this._sShortName = oOptions.sShortName == undefined ? '' : oOptions.sShortName;
+    this._sSafariWebId = oOptions.sSafariWebId == undefined ? '' : oOptions.sSafariWebId;
+    this._sSubfolder = oOptions.sSubfolder == undefined ? '/plugins_public/onesignal/' : oOptions.sSubfolder;
+    this._sNotificationUrl = oOptions.sNotificationUrl == undefined ? '' : oOptions.sNotificationUrl;
+
+    this._sTxtNotificationRequest = oOptions.sTxtNotificationRequest == undefined ? 'Would you like to get notifications' : oOptions.sTxtNotificationRequest;
+    this._sTxtNotificationRequestYes = oOptions.sTxtNotificationRequestYes == undefined ? 'Yes' : oOptions.sTxtNotificationRequestYes;
+    this._sTxtNotificationRequestNo = oOptions.sTxtNotificationRequestNo == undefined ? 'No' : oOptions.sTxtNotificationRequestNo;
+
+    var $this = this;
+    $(document).ready(function() {
+    	$this.init();
+    });
+}
+
+BxDolPush.prototype.init = function() {
+	var $this = this;
+	window.OneSignal = window.OneSignal || [];
+
+	OneSignal.push(function() {
+		OneSignal.SERVICE_WORKER_UPDATER_PATH = "OneSignalSDKUpdaterWorker.js.php";
+    	OneSignal.SERVICE_WORKER_PATH = "OneSignalSDKWorker.js.php";
+    	OneSignal.SERVICE_WORKER_PARAM = { scope: '/' }; /* This registers the workers at the root scope, which is allowed by the HTTP header "Service-Worker-Allowed: /" */
+	});
+
+	OneSignal.push(["init", {
+		appId: $this._sAppId,
+		autoRegister: true, /* Set to true to automatically prompt visitors */
+		subdomainName: $this._sShortName, /* required only for http  sites */
+		safari_web_id: $this._sSafariWebId,
+		path: $this._sSubfolder, /* A trailing slash is required */
+		persistNotification: false,
+		welcomeNotification: {
+			disable:false
+		},
+		httpPermissionRequest: {
+			enable: true
+		},
+		promptOptions: {
+			/* These prompt options values configure both the HTTP prompt and the HTTP popup. */
+			/* actionMessage limited to 90 characters */
+			actionMessage: $this._sTxtNotificationRequest, 
+			/* acceptButtonText limited to 15 characters */
+			acceptButtonText: $this._sTxtNotificationRequestYes,
+			/* cancelButtonText limited to 15 characters */
+			cancelButtonText: $this._sTxtNotificationRequestNo
+		}
+	}]);
+
+	OneSignal.push(function() {
+		var isPushSupported = OneSignal.isPushNotificationsSupported();
+		OneSignal.setDefaultNotificationUrl($this._sNotificationUrl);
+		OneSignal.isPushNotificationsEnabled().then(function(isEnabled) {								
+			OneSignal.sendTag('user', $this._iProfileId); // set tag for onesiganl, this tag will be used to send notification
+			if(!isEnabled && isPushSupported)
+				OneSignal.showHttpPermissionRequest();													
+		});
+	});
+
+    if (navigator.userAgent.indexOf('gonative') > -1) {
+        var data = {user: $this._iProfileId};
+        var json = JSON.stringify(data);
+        $(document).ready(function () {
+            window.location.href='gonative://registration/send?customData=' + encodeURIComponent(json);        
+        });
+    }
+};

+ 483 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/js/classes/BxDolUploader.js

@@ -0,0 +1,483 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+/**
+ * Simple Uploader js class
+ */
+function BxDolUploaderSimple (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._sIframeId = 'bx-form-input-files-' + sUniqId + '-iframe';
+    this._eForm = null;
+}
+
+BxDolUploaderSimple.prototype.init = function (sUploaderObject, sStorageObject, sUniqId, options) {
+    this._isUploadsInProgress = false;
+
+    this._sUploaderObject = sUploaderObject;
+    this._sStorageObject = sStorageObject;
+
+    this._sUniqId = sUniqId;
+
+    this._sUploaderJsInstance = 'glUploader_' + sUniqId + '_' + this._sUploaderObject;
+    this._sUploadInProgressContainerId = 'bx-form-input-files-' + sUniqId + '-upload-in-progress-' + this._sUploaderObject;
+    this._sPopupContainerId = 'bx-form-input-files-' + sUniqId + '-popup-wrapper-' + this._sUploaderObject;
+
+    this._sResultContainerId = 'bx-form-input-files-' + sUniqId + '-upload-result';
+    this._sErrorsContainerId = 'bx-form-input-files-' + sUniqId + '-errors'; 
+
+    this._sFormContainerId = 'bx-form-input-files-' + sUniqId + '-form-cont';
+
+    this._sTemplateGhost = options.template_ghost ? options.template_ghost : '<div id="' + this._getFileContainerId('{file_id}') + '"><input type="hidden" name="f[]" value="{file_id}" />{file_name} (<a href="javascript:void(0);" onclick="{js_instance_name}.deleteGhost(\'{file_id}\')">delete</a>)</div>';
+    this._sTemplateError = options.template_error_msg ? options.template_error_msg : '<div>{error}</div>' ;
+    this._sTemplateErrorGhosts = options.template_error_ghosts ? options.template_error_ghosts : this._sTemplateError;
+
+    this._isMultiple = undefined == options.multiple || !options.multiple ? false : true;
+
+    this._iContentId = undefined == options.content_id || '' == options.content_id ? '' : parseInt(options.content_id);
+
+    this._sImagesTranscoder = options.images_transcoder ? options.images_transcoder : '';
+
+    this._isPrivate = undefined == options.storage_private || parseInt(options.storage_private) ? 1 : 0;
+
+    this._isErrorShown = false;
+}
+
+BxDolUploaderSimple.prototype.isMultiple = function () {    
+    return this._isMultiple;
+}
+
+BxDolUploaderSimple.prototype.getCurrentFilesCount = function () {
+    return $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').length;
+}
+
+BxDolUploaderSimple.prototype.showUploaderForm = function () {
+    var $this = this;
+    var sUrl = this._getUrlWithStandardParams() + '&a=show_uploader_form&m=' + (this._isMultiple ? 1 : 0) + '&c=' + this._iContentId + '&p=' + this._isPrivate + '&_t=' + escape(new Date());
+
+    $(window).dolPopupAjax({
+        url: sUrl,
+        id: {force: true, value: this._sPopupContainerId},
+        onBeforeShow: function() {
+            if ($this.isMultiple())
+                $('#' + $this._sPopupContainerId + ' .bx-uploader-add-more-files').show();
+            else
+                $('#' + $this._sPopupContainerId + ' .bx-uploader-add-more-files').hide();
+            $('#' + $this._sPopupContainerId + ' .bx-popup-element-close').click(function() {
+                $this.onClickCancel();
+            });
+            if ('undefined' !== typeof($this.onBeforeShowPopup))
+                $this.onBeforeShowPopup();
+        },
+        onShow: function () {
+            if ('undefined' !== typeof($this.onShowPopup))
+                $this.onShowPopup();
+        },
+        closeElement: false,
+        closeOnOuterClick: false
+    });
+}
+
+BxDolUploaderSimple.prototype.onClickCancel = function () {
+	var $this = this;
+    if (this._isUploadsInProgress) {
+    	bx_confirm(_t('_sys_uploader_confirm_close_popup'), function() {
+    		$this.cancelAll();
+            $('#' + $this._sPopupContainerId).dolPopupHide({});
+    	});
+    } else {
+        $('#' + this._sPopupContainerId).dolPopupHide();
+    }
+}
+
+BxDolUploaderSimple.prototype.onBeforeUpload = function (params) {
+
+    this._eForm = params;
+
+    this._loading(true, false);
+        
+    this._isUploadsInProgress = true;
+    this._lockPageFromLeaving();
+}
+
+BxDolUploaderSimple.prototype.onProgress = function (params) {
+
+}
+
+BxDolUploaderSimple.prototype.onUploadCompleted = function (sErrorMsg) {
+
+    this._isUploadsInProgress = false;
+    this._unlockPageFromLeaving();
+    this._loading(false, true);
+
+    if (sErrorMsg.length) {
+        this.restoreGhosts();        
+        this._showError(sErrorMsg);
+    } else {        
+        this.restoreGhosts();
+        $('#' + this._sPopupContainerId).dolPopupHide({});
+    }
+}
+
+BxDolUploaderSimple.prototype.cancelAll = function () {
+    $('#' + this._sIframeId).attr('src', 'javascript:false;');
+    this.onUploadCompleted(_t('_sys_uploader_upload_canceled'));
+}
+
+BxDolUploaderSimple.prototype.restoreGhosts = function () {
+    var sUrl = this._getUrlWithStandardParams() + '&img_trans=' + this._sImagesTranscoder + '&a=restore_ghosts&f=json' + '&c=' + this._iContentId + '&_t=' + (new Date());
+    var $this = this;
+
+    bx_loading(this._sResultContainerId, true);
+
+    $.getJSON(sUrl, function (aData) {
+
+        bx_loading($this._sResultContainerId, false);
+
+        if (!$this.isMultiple())
+            $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').remove();
+
+        if ('object' === typeof(aData)) {
+            $.each(aData, function(iFileId, oVars) {
+            	var oFileContainer = $('#' + $this._getFileContainerId(iFileId));
+                if (oFileContainer.length > 0)
+                    return;
+                var sHTML;
+                if (typeof $this._sTemplateGhost == 'object')
+                    sHTML = $this._sTemplateGhost[iFileId];
+                else
+                    sHTML = $this._sTemplateGhost;
+                for (var i in oVars)
+                    sHTML = sHTML.replace (new RegExp('{'+i+'}', 'g'), oVars[i]);
+                
+                $('#' + $this._sResultContainerId).prepend(sHTML);
+
+                oFileContainer.find('.bx-uploader-ghost-preview img').hide().fadeIn(1000);
+            });
+        }
+    });
+};
+
+BxDolUploaderSimple.prototype.deleteGhost = function (iFileId) {
+    var sUrl = this._getUrlWithStandardParams() + '&a=delete&id=' + iFileId;
+    var $this = this;
+
+    var sFileContainerId = $this._getFileContainerId(iFileId);
+    bx_loading(sFileContainerId, true);
+
+    $.post(sUrl, {_t: new Date()}, function (sMsg) {
+        bx_loading(sFileContainerId, false);
+        if ('ok' == sMsg) {
+            $('#' + sFileContainerId).slideUp('slow', function () {
+                $(this).remove();
+            });
+        } else {
+            $('#' + this._sResultContainerId).prepend($this._sTemplateErrorGhosts.replace('{error}', sMsg));
+        }        
+    });
+};
+
+BxDolUploaderSimple.prototype._clearErrors = function () {
+    $('#' + this._sPopupContainerId + ' #' + this._sErrorsContainerId).html('');
+    this._isErrorShown = false;
+}
+
+BxDolUploaderSimple.prototype._showError = function (s, bAppend) {
+    if (s == undefined || !s.length)
+        return;
+    if (!bAppend)
+        $('#' + this._sPopupContainerId + ' #' + this._sErrorsContainerId).html(this._sTemplateError.replace('{error}', s));
+    else
+        $('#' + this._sPopupContainerId + ' #' + this._sErrorsContainerId).prepend(this._sTemplateError.replace('{error}', s));
+    this._isErrorShown = true;
+};
+
+BxDolUploaderSimple.prototype._getFileContainerId = function (iFileId) {
+	return 'bx-uploader-file-' + this._sStorageObject + '-' + iFileId;
+};
+
+BxDolUploaderSimple.prototype._getUrlWithStandardParams = function () {
+    return sUrlRoot + 'storage_uploader.php?uo=' + this._sUploaderObject + '&so=' + this._sStorageObject + '&uid=' + this._sUniqId;
+}
+
+BxDolUploaderSimple.prototype._lockPageFromLeaving = function () {
+    $(window).bind('beforeunload', function (e) {
+        var e = e || window.event;
+        // for ie, ff
+        e.returnValue = _t('_sys_uploader_confirm_leaving_page');
+        // for webkit
+        return _t('_sys_uploader_confirm_leaving_page');
+    });
+}
+
+BxDolUploaderSimple.prototype._unlockPageFromLeaving = function () {
+    $(window).unbind('beforeunload');
+}
+
+BxDolUploaderSimple.prototype._loading = function (bShowProgress, bShowForm) {
+
+    var eForm = $('#' + this._sFormContainerId + ' .bx-uploader-files-list');
+    var eBtn = $('#' + this._sFormContainerId + ' .bx-btn-primary');
+
+    if (bShowForm) {
+        if (null != this._eForm) {
+            eForm.find('.bx-uploader-simple-file').filter(':not(:first)').remove();
+            if ('undefined' !== typeof(this._eForm.reset)) 
+                this._eForm.reset();
+        }
+        eForm.show();
+        eBtn.show();
+    } else {
+        eForm.hide();
+        eBtn.hide();
+    }
+
+    bx_loading($('#' + this._sFormContainerId + ' .bx-uploader-loading').get(0), bShowProgress);
+}
+
+/**
+ * HTML5 Uploader js class
+ */
+function BxDolUploaderHTML5 (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._sIframeId = 'bx-form-input-files-' + sUniqId + '-iframe';
+    this._eForm = null;    
+
+    this._sDivId = 'bx-form-input-files-' + sUniqId + '-div-' + this._sUploaderObject;
+
+    this._sFocusDivId = 'bx-form-input-files-' + sUniqId + '-focus-' + this._sUploaderObject;
+
+    this._uploader = null;
+
+    this.initUploader = function (o) {
+
+        var $this = this;
+
+        if (null != this._uploader)
+            this._uploader = null;
+
+        var _options = {
+            url: sUrlRoot + 'storage_uploader.php',
+            uploadMultiple: false,
+            timeout: 0,
+            maxFiles: $this.isMultiple() ? 50 : 1,
+            params: {
+                uo: this._sUploaderObject,
+                so: this._sStorageObject,
+                uid: this._sUniqId,
+                m: this._isMultiple ? 1 : 0,
+                c: this._iContentId,
+                p: this._isPrivate,
+                a: "upload"
+            },
+        };
+
+        this._uploader = new Dropzone('#' + this._sDivId, $.extend({}, _options, o));
+        
+        this._uploader.on('queuecomplete', function (file) {
+            $this.onUploadCompleted('');
+        });
+        this._uploader.on('addedfile', function (file) {
+            $this.onBeforeUpload('');
+        });        
+        this._uploader.on('totaluploadprogress', function (uploadProgress) {
+            $this.onProgress({
+                total: uploadProgress
+            });
+        });
+        this._uploader.on('success', function (oFile, sJSON) {
+            var o = null;
+            try {
+                o = JSON.parse(sJSON);
+            } 
+            catch (e) {}
+
+            if (o && 'undefined' !== typeof(o.error)) {
+                $this._uploader.removeFile(oFile);
+                $this._showError(o.error, true);
+            }
+        });
+        this._uploader.on('error', function (oFile, sErrorMsg) {
+            $this._uploader.removeFile(oFile);
+            $this._showError(sErrorMsg, true);
+        });
+
+        this.initPasteEditor();
+    }
+
+    this.onUploadCompleted = function (sErrorMsg) {        
+
+        if (sErrorMsg.length)
+            this._showError(sErrorMsg);        
+        
+        if (0 == this._uploader.getQueuedFiles().length && 0 == this._uploader.getUploadingFiles().length) {
+            this._isUploadsInProgress = false;
+            this.restoreGhosts();
+            if (!this._isErrorShown) {
+                $('#' + this._sPopupContainerId).dolPopupHide({});
+                this._uploader.removeAllFiles();
+            }
+        }
+    }
+
+    this.cancelAll = function () {
+        this._uploader.removeAllFiles(true);
+    }
+
+    this.onBeforeUpload = function (params) {
+        this._isUploadsInProgress = true;
+    }
+
+    this.onProgress = function (params) {
+
+    }
+
+    this.onClickCancel = function () {
+        this._uploader.removeAllFiles(true);
+        BxDolUploaderSimple.prototype._clearErrors.call(this);
+        BxDolUploaderSimple.prototype.onClickCancel.call(this);
+    }
+
+    this.onShowPopup = function () {
+        var $this = this;
+        setTimeout(function () {
+            $this.focusPasteEditor();
+        }, 200);
+    }
+
+    this.focusPasteEditor = function () {
+        $('#' + this._sFocusDivId).focus();
+    }
+
+    this.initPasteEditor = function () {
+        var $this = this;
+        $('#' + this._sFocusDivId).on('paste', function (e) {
+            var aFiles = [];
+            if (e.type == 'paste') {
+                var aItems = (e.clipboardData || e.originalEvent.clipboardData).items;
+                
+                for (var i in aItems) {
+                    var oItem = aItems[i];
+                    if (oItem.kind === 'file')
+                        aFiles.push(oItem.getAsFile());
+                }
+                $this._uploader.handleFiles(aFiles);
+            }
+        });
+    }
+}
+
+BxDolUploaderHTML5.prototype = BxDolUploaderSimple.prototype;
+
+/**
+ * Crop Image Uploader js class
+ */
+function BxDolUploaderCrop (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._eForm = null;
+
+    this.initUploader = function (oOptions) {
+
+        var $this = this;
+
+        var aExt = ['jpg', 'jpeg', 'png', 'gif'];
+
+        var eCroppie = $("#" + this._sFormContainerId + " .bx-croppie-element").croppie(oOptions);
+
+		$("#" + this._sFormContainerId + ' .bx-crop-rotate').on('click', function(ev) {
+			eCroppie.croppie('rotate', parseInt($(this).data('deg')));
+		});
+        
+        $("#" + this._sFormContainerId + " input[name=f]").on("change", function() {
+            var input = this;            
+
+            if (input.files && input.files[0]) {
+
+                var m = input.files[0].name.match(/\.([A-Za-z0-9]+)$/);
+
+                if (2 != m.length || -1 == aExt.indexOf(m[1].toLowerCase())) {                    
+                    $(input).replaceWith($(input).val('').clone(true));
+                    $this._showError(_t('_sys_uploader_crop_wrong_ext'));
+                    return;
+                }
+
+                $this._clearErrors();
+
+                var reader = new FileReader()
+
+                reader.onload = function(e) {
+                    eCroppie.croppie('bind', {
+                        url: e.target.result
+                    });
+                    $("#" + $this._sFormContainerId + " .bx-croppie-element").addClass('ready');
+                    $("#" + $this._sFormContainerId + " .bx-crop-action").removeClass('bx-btn-disabled');
+                    $("#" + $this._sFormContainerId + " .bx-croppie-element").data('bx-filename', input.files[0].name.replace(/(\.[A-Za-z0-9]+)$/, '.jpg'));
+                }
+                reader.readAsDataURL(input.files[0]);
+            }
+        });
+
+        $("#" + this._sFormContainerId + " .bx-crop-upload").on('click', function(ev) {
+            eCroppie.croppie('result', {
+                type: 'canvas',
+                size: 'original',
+                format: 'jpeg',
+                quality: '0.85',
+            }).then(function(resp) {
+                var fd = new FormData();
+
+                fd.append("f", dataURItoBlob(resp), $("#" + $this._sFormContainerId + " .bx-croppie-element").data('bx-filename'));
+                $.each(oOptions.bx_form, function (sName) {
+                    fd.append(sName, this);
+                });
+                
+                $this.onBeforeUpload(fd);
+
+                $.ajax({
+                    url: sUrlRoot + 'storage_uploader.php',
+                    type: "POST",
+                    processData: false,
+                    contentType: false,
+                    data: fd,
+                    success: function(data) {
+                        eval(data);
+                    },
+                    error: function() {
+                        $this._showError(_t('_sys_uploader_crop_err_upload'));
+                    }
+                })
+
+            });
+        });
+
+        function dataURItoBlob(dataURI) {
+            var split = dataURI.split(','),
+                dataTYPE = split[0].match(/:(.*?);/)[1],
+                binary = atob(split[1]),
+                array = [];
+                
+            for (var i = 0; i < binary.length; i++) 
+                array.push(binary.charCodeAt(i));
+
+            return new Blob([new Uint8Array(array)], {
+                type: dataTYPE
+            });
+        }
+
+        
+    };
+}
+
+BxDolUploaderCrop.prototype = BxDolUploaderSimple.prototype;
+
+/** @} */

+ 1101 - 0
upgrade/files/10.0.0-10.1.0.B1/files/inc/js/functions.js

@@ -0,0 +1,1101 @@
+/**
+ * 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) {
+    var bBulk = !$.isNumeric(iProfileId);
+    var iAclCard = !bBulk && $('#sys-acl-profile-' + iProfileId).length > 0 ? 1 : 0;
+
+    var bLoading = typeof(mixedLoadingElement) != 'undefined';
+    if(bLoading)
+        bx_loading($(mixedLoadingElement), true);
+
+    $.post(sUrlRoot + 'menu.php', {o:'sys_set_acl_level', profile_id: iProfileId, acl_level_id: iAclLevel, acl_card:iAclCard}, function(oData) {
+        if(bLoading)
+            bx_loading($(mixedLoadingElement), false);
+
+        if(oData.msg != undefined && oData.msg.length) {
+            bx_alert(oData.msg);
+            return;
+        }
+
+        if($(mixedLoadingElement).hasClass('bx-popup-applied'))
+            $(mixedLoadingElement).dolPopupHide().remove();
+
+        if(typeof(oData.card) == 'object')
+            for(var iField in oData.card) {
+                var oCard = $(oData.card[iField]);
+                $('#' + oCard.attr('id')).replaceWith(oCard);
+            }
+    }, 'json');
+}
+
+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, iMinLen) {
+    var oForm = $(e.target).parents(sFormSel + ':first');
+
+    if ('undefined' != typeof(e) && 13 == e.keyCode) {
+        oForm.find('input[name=live_search]').val(0);
+        oForm.submit();
+        return false;
+    }
+
+    iMinLen = typeof(iMinLen) != 'undefined' ? parseInt(iMinLen) : 0;
+    if(iMinLen > 0 && iMinLen > (oForm.find('input[name=keyword]').val().length + 1))
+        return true;
+
+    if ('undefined' != typeof(glBxSearchTimeoutHandler) && glBxSearchTimeoutHandler)
+        clearTimeout(glBxSearchTimeoutHandler);
+
+    glBxSearchTimeoutHandler = setTimeout(function () {
+        bx_search (n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults);
+    }, 500);
+
+    return true;
+}
+
+function bx_search (n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults) {
+    if ('undefined' == typeof(sLoadingContSel))
+        sLoadingContSel = sResultsContSel;
+    if ('undefined' == typeof(bSortResults))
+        bSortResults = false;
+
+    var sQuery = $('input', sFormSel).serialize();
+
+    bx_loading($(sLoadingContSel), true);
+    $.post(sUrlRoot + 'searchKeywordContent.php', sQuery, function(data) {
+        bx_loading($(sLoadingContSel), false);
+        if (bSortResults) {
+            var aSortedUnits = $(data).find(".bx-def-unit-live-search").toArray().sort(function (a, b) {
+                return b.getAttribute('data-ts') - a.getAttribute('data-ts');
+            });
+            data = '';
+            $.each(aSortedUnits.slice(0, n), function (i, e) {
+                data += e.outerHTML;
+            });
+        } 
+        $(sResultsContSel).html(data).bxProcessHtml();
+    });
+
+    return false;
+}
+
+function on_filter_apply(e, sInputId, sFilterName)
+{
+	var oRegExp = new RegExp("[&]{0,1}" + sFilterName + "=.*");
+    var s = ('' + document.location).replace(oRegExp, ''); // remove filter
+    s = s.replace(/page=\d+/, 'page=1'); // goto 1st page
+    if (e.checked && $('#' + sInputId).val().length > 2)
+        s += (-1 == s.indexOf('?') ? '?' : '&') + sFilterName + '=' + $('#' + sInputId).val(); // append filter
+    document.location = s;
+}
+
+function on_filter_key_up (e, sCheckboxId)
+{
+    if (13 == e.keyCode) {
+        $('#' + sCheckboxId).click();
+        return false;
+    }
+    else {
+        $('#' + sCheckboxId).removeAttr('checked');
+        return true;
+    }
+}
+
+function on_copyright_click()
+{
+	oDate = new Date();
+
+	$(document).dolPopupAlert({
+		message: _t('_copyright', oDate.getFullYear())
+	});
+}
+
+function bx_activate_anim_icons(sColor)
+{
+    if ('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, oParams)
+{
+    $(document).dolPopupAlert({
+        message: sMessage,
+        onClickOk: fOnClickOk,
+        params: oParams
+    });
+}
+
+function bx_confirm(sMessage, fOnClickYes, fOnClickNo, oParams)
+{
+    $(document).dolPopupConfirm({
+        message: sMessage,
+        onClickYes: fOnClickYes,
+        onClickNo: fOnClickNo,
+        params: oParams
+    });
+}
+
+function bx_prompt(sMessage, sValue, fOnClickOk, fOnClickCancel, oParams)
+{
+    $(document).dolPopupPrompt({
+        message: sMessage,
+        value: sValue,
+        onClickOk: fOnClickOk,
+        onClickCancel: fOnClickCancel,
+        params: oParams
+    });
+}
+
+/**
+ * This function loads the passed list of js files and executes callback function when the all files are loaded.
+ * In case when this function is called for dynamic loading of the same file but many times (like Plyr video init)
+ * it allows to avoid multiply loadings of the same file.
+ */
+function bx_get_scripts (aFiles, fCallback)
+{
+    let iCounter = 0;
+    const iLength = aFiles.length,
+          // this function is called every time when each file with unique name is started to load
+          fDone = (iCounter) => {
+              let sFile = aFiles[iCounter];
+              // init file's record in global scope with loaded = false, means that file is not loaded yet
+              glBxLoadedScripts[sFile] = { loaded: false, queue: []};
+              $.bxGetCachedScript(sFile).done(() => {
+                  // set "loaded" flag to true when the file is loaded
+                  glBxLoadedScripts[sFile].loaded = true;
+                  // executes the all callback functions which was attached to queue
+                  // during multiple loadings of the same file
+                  glBxLoadedScripts[sFile].queue.map(sFunc => sFunc());
+                  // This part is executed only when the last file from the list is loaded
+                   if (((iLength-1) === iCounter) && typeof fCallback === 'function')
+                       fCallback();
+                   else
+                        fDone(iCounter + 1);
+              });
+          };
+
+    if ('undefined' === typeof(glBxLoadedScripts))
+        glBxLoadedScripts = {};
+
+        let sFile = aFiles[iCounter];
+        if ('undefined' === typeof(glBxLoadedScripts[sFile])) {
+            // call the function if the file doesn't exist in global scope yet
+            fDone(iCounter);
+        }
+        else if (typeof fCallback === 'function') {
+            // this condition is executed only in case when there are many files with the same name are loading
+            // and current file already exists in global scope, but is not completely loaded (loaded = false)
+            if (glBxLoadedScripts[sFile].loaded === false)
+                glBxLoadedScripts[sFile].queue.push(fCallback); // attache callback to queue
+            else
+                fCallback(); // executes callback only in case when the file is completely loaded and exists in global scope
+        }
+}
+
+jQuery.bxGetCachedScript = function(sUrl, oOptions) 
+{
+    oOptions = $.extend(oOptions || {}, {
+        dataType: "script",
+        cache: true,
+        url: sUrl
+    });
+
+    return jQuery.ajax(oOptions);
+};
+
+/** @} */

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

+ 140 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/connect/classes/BxBaseModConnectDb.php

@@ -0,0 +1,140 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseConnect Base classes for OAuth connect modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModConnectDb extends BxBaseModGeneralDb
+{
+    protected $sTablePrefix;
+
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+
+        $this -> sTablePrefix = $oConfig -> getDbPrefix();
+    }
+    
+    /**
+     * Get local profile id by remote profile id
+     *
+     * @param $iRemoteId remote profile id
+     * @return local profile id
+     */
+    function getProfileId($iRemoteId)
+    {
+        $sQuery = $this->prepare ("SELECT `local_profile` FROM `{$this -> sTablePrefix}accounts` WHERE `remote_profile` = ? LIMIT 1", $iRemoteId);
+        return $this->getOne($sQuery);
+    }
+
+    /**
+     * Get remote profile id by local profile id
+     *
+     * @param $iLocalId local profile id
+     * @return remote profile id
+     */
+    function getRemoteProfileId($iLocalId)
+    {
+        $sQuery = $this->prepare ("SELECT `remote_profile` FROM `{$this -> sTablePrefix}accounts` WHERE `local_profile` = ? LIMIT 1", (int)$iLocalId);
+        return $this->getOne($sQuery);
+    }
+    
+    /**
+     * Save new remote ID
+     *
+     * @param $iProfileId integer
+     * @param $iRemoteId integer
+     * @return bool
+     */
+    function saveRemoteId($iProfileId, $iRemoteId)
+    {
+        $iRemoteId = $iRemoteId;
+        $iProfileId = (int) $iProfileId;
+
+        $sQuery = $this->prepare ("REPLACE INTO `{$this -> sTablePrefix}accounts` SET `local_profile` = ?, `remote_profile` = ?", $iProfileId, $iRemoteId);
+        return $this -> query($sQuery);
+    }
+
+    /**
+     * Delete remote account
+     *
+     * @param $iProfileId integer
+     * @return void
+     */
+    function deleteRemoteAccount($iProfileId)
+    {
+        $iProfileId = (int) $iProfileId;
+
+        $sQuery = $this->prepare ("DELETE FROM `{$this -> sTablePrefix}accounts` WHERE `local_profile` = ?", $iProfileId);
+        return $this -> query($sQuery);
+    }
+
+    /**
+     * Make as friends
+     *
+     * @param $iMemberId integer
+     * @param $iProfileId intger
+     * @return void
+     */
+    function makeFriend($iMemberId, $iProfileId)
+    {
+        // TODO:
+        return null;
+    }
+
+    /**
+     * Create new profile;
+     *
+     * @param  : (array) $aProfileFields    - `Profiles` table's fields;
+     * @return : (integer)  - profile's Id;
+     */
+    function createProfile(&$aProfileFields)
+    {
+        // TODO:
+        return null;
+    }
+
+    /**
+     * Function will update  profile's status;
+     *
+     * @param  : $iProfileId (integer) - profile's Id;
+     * @param  : $sStatus    (string)  - profile's status;
+     * @return : void;
+     */
+    function updateProfileStatus($iProfileId, $sStatus)
+    {
+        // TODO:
+        return null;
+    }
+
+    /**
+     * Function will check field name in 'Profiles` table;
+     *
+     * @param $sFieldName string
+     * @return : (boolean);
+     */
+    function isFieldExist($sFieldName)
+    {
+        // TODO:
+        return null;
+    }
+
+    /**
+     * Check existing email
+     *
+     * @param $sEmail string
+     * @return boolean
+     */
+    function isEmailExisting($sEmail)
+    {
+        // TODO:
+        return null;
+    }
+}
+
+/** @} */

+ 596 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php

@@ -0,0 +1,596 @@
+<?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;
+
+    /**
+     * 'Ajax Mode' determines the format of response. If it's TRUE the response 
+     * (a form or an error appeared during form creation) should be returned 
+     * as text during initial loading, while all other responses appeared after 
+     * form submit should be arrays, which are ready to path to echoJson.
+     */
+    protected $_bAjaxMode;
+
+    /**
+     * Use absolute Action URL in generated form object. 
+     * It's needed in Ajax Mode.
+     */
+    protected $_bAbsoluteActionUrl;
+
+    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);
+
+        $this->_bAbsoluteActionUrl = false;
+        $mixedAbsoluteActionUrl = bx_get('absolute_action_url');
+        if($mixedAbsoluteActionUrl !== false)
+            $this->setAbsoluteActionUrl($mixedAbsoluteActionUrl);
+    }
+
+    public function setAjaxMode($bAjaxMode)
+    {
+        $this->_bAjaxMode = (bool)$bAjaxMode;
+        if($this->_bAjaxMode)
+            $this->setDynamicMode(true);
+    }
+
+    public function setAbsoluteActionUrl($bAbsoluteActionUrl)
+    {
+        $this->_bAbsoluteActionUrl = (bool)$bAbsoluteActionUrl;
+    }
+
+    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'];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('add', $oForm);
+
+        return $oForm; 
+    }
+
+    public function getObjectFormEdit ($sDisplay = false)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('edit', $oForm);
+
+        return $oForm;
+    }
+
+    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'];
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('delete', $oForm);
+
+        return $oForm;
+    }
+
+    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);
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        /*
+         * Process metas.
+         * Note. It's essential to process metas a the very end, 
+         * because all data related to an entry should be already
+         * processed and are ready to be passed to alert. 
+         */
+        $this->_oModule->processMetasAdd($iContentId);
+
+        // Create alert about the completed action.
+        $this->_oModule->alertAfterAdd($aContentInfo);
+
+        return array('code' => 0, 'message' => '', 'content' => $aContentInfo);
+    }
+
+    public function addDataForm ($sDisplay = false, $sCheckFunction = false)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedAdd';
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bAsJson = false;
+
+        // get form object
+        $oForm = $this->getObjectFormAdd($sDisplay);
+        if (!$oForm)
+            return $this->prepareResponse(MsgBox(_t('_sys_txt_error_occured')), $bAsJson, 'msg');
+
+        $bAsJson = $this->_bAjaxMode && $oForm->isSubmitted();
+
+        // 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')), $bAsJson, 'msg');
+            else
+                return $this->prepareResponse(MsgBox($sMsg), $bAsJson, 'msg');
+        }
+
+        // check and display form
+        $oForm->initChecker();
+        if (!$oForm->isSubmittedAndValid())
+            return $this->prepareResponse($oForm->getCode($this->_bDynamicMode), $bAsJson, '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), $bAsJson, 'form', array(
+                    'form_id' => $oForm->getId()
+                ));
+            else
+                return $this->prepareResponse(MsgBox(_t('_sys_txt_error_entry_creation')), $bAsJson, 'msg');
+        }
+
+        $sResult = $this->onDataAddAfter (getLoggedId(), $iContentId);
+        if ($sResult)
+            return $this->prepareResponse($sResult, $bAsJson, 'msg');
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        /*
+         * Process metas.
+         * Note. It's essential to process metas a the very end, 
+         * because all data related to an entry should be already
+         * processed and are ready to be passed to alert. 
+         */
+        $this->_oModule->processMetasAdd($iContentId);
+
+        // Perform ACL action
+        $this->_oModule->$sCheckFunction(true);
+
+        // Create alert about the completed action.
+        $this->_oModule->alertAfterAdd($aContentInfo);
+
+        // Redirect
+        $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) {
+            echoJson($this->prepareResponse($sUrl, $this->_bAjaxMode, 'redirect'));
+            exit;
+        }
+        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, $oProfile, $oForm);
+
+        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 metas.
+         * Note. It's essential to process metas a the very end, 
+         * because all data related to an entry should be already
+         * processed and are ready to be passed to alert. 
+         */
+        $this->_oModule->processMetasEdit($iContentId, $oForm);
+
+        // Perform ACL action
+        $this->_oModule->$sCheckFunction($aContentInfo, true);
+
+        // Create alert about the completed action.
+        $this->_oModule->alertAfterEdit($aContentInfo);
+        
+        // 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->$sCheckFunction($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)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->deleteGroupCustomByContentId($iContentId);
+
+        return '';
+    }
+
+    public function onDataEditBefore ($iContentId, $aContentInfo, &$aTrackTextFieldsChanges, &$oProfile, &$oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(isset($CNF['FIELD_STATUS']) && isset($aContentInfo[$CNF['FIELD_STATUS']]) && $aContentInfo[$CNF['FIELD_STATUS']] == 'failed')
+            $oForm->addTrackFields($CNF['FIELD_STATUS'], $aContentInfo);
+    }
+
+    public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        /*
+         * Load update data.
+         */
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if(isset($CNF['FIELD_PHOTO']))
+            $oForm->processFiles ($CNF['FIELD_PHOTO'], $iContentId, false);
+
+        if(isset($CNF['FIELD_STATUS']) && ($aTrackResult = $oForm->isTrackFieldChanged($CNF['FIELD_STATUS'], true)) !== false)
+            if($aTrackResult['old'] == 'failed' && $aTrackResult['new'] == 'active')
+                $this->_oModule->alertAfterAdd($aContentInfo);
+
+        if(isset($CNF['FIELD_ALLOW_VIEW_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->reassociateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
+        return '';
+    }
+
+    public function onDataAddAfter ($iAccountId, $iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if(($oForm = $this->getObjectFormAdd()) !== false) {
+            if(isset($CNF['FIELD_PHOTO']))
+                $oForm->processFiles($CNF['FIELD_PHOTO'], $iContentId, true);
+        }
+
+        if(isset($CNF['FIELD_ALLOW_VIEW_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false)
+            $oPrivacy->associateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]);
+
+        return '';
+    }
+
+    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,
+            '_dt' => 'json'
+        );
+
+        if(!empty($aAdditional) && is_array($aAdditional))
+            $aResponse = array_merge($aResponse, $aAdditional);
+
+        return $aResponse;
+    }
+    
+    protected function _setAbsoluteActionUrl($sType, &$oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sKeyUri = 'URI_' . strtoupper($sType) . '_ENTRY';
+        if(empty($CNF[$sKeyUri]))
+            return;
+
+        $oForm->setAbsoluteActionUrl(BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF[$sKeyUri]));
+    }
+}
+
+/** @} */

+ 1879 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/general/classes/BxBaseModGeneralModule.php

@@ -0,0 +1,1879 @@
+<?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('BxDolAcl');
+
+/**
+ * Base module class.
+ */
+class BxBaseModGeneralModule extends BxDolModule
+{
+    protected $_iProfileId;
+    protected $_aSearchableNamesExcept;
+    protected $_aFormParams;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+        $this->_aSearchableNamesExcept = array(
+            'allow_view_to'
+        );
+
+        $this->_aFormParams = array(
+            'display' => false, 
+            'dynamic_mode' => false, 
+            'ajax_mode' => false, 
+            'absolute_action_url' => false,
+            'visibility_autoselect' => false,
+            'context_id' => 0,
+            'custom' => array()
+        );
+    }
+
+    // ====== ACTIONS METHODS
+
+    public function actionRss ()
+    {
+        $aArgs = func_get_args();
+        $this->_rss($aArgs);
+    }
+    
+    public function actionGetCreatePostForm()
+    {
+        $sName = $this->_oConfig->getName();
+
+    	$aParams = bx_process_input(array_intersect_key($_GET, $this->_aFormParams));
+    	$aParams = array_merge($this->_aFormParams, $aParams);
+        $aParams['context_id'] = (bool)$aParams['context_id'] ? (int)$aParams['context_id'] : false;
+
+    	$mixedResponse = $this->serviceGetCreatePostForm($aParams);
+    	if(empty($mixedResponse))
+            return echoJson(array());
+        else if(is_array($mixedResponse)) {
+            $mixedResponse['module'] = $sName;
+            return echoJson($mixedResponse);
+        }
+
+        return echoJson(array(
+            'module' => $sName,
+            'content' => $mixedResponse
+    	));
+    }
+
+    // ====== SERVICE METHODS
+    public function serviceGetCreatePostForm($aParams = array())
+    {
+    	$aParams = array_merge($this->_aFormParams, $aParams);
+
+    	$oForm = $this->serviceGetObjectForm('add', $aParams);
+    	if(!$oForm)
+            return ''; 	
+
+    	return $this->serviceEntityCreate($aParams);
+    }
+
+    public function serviceGetAuthor ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_AUTHOR', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0; 
+    }
+
+    public function serviceGetDateAdded ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_ADDED', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0; 
+    }
+
+    public function serviceGetDateChanged ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_CHANGED', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0;
+    }
+
+    public function serviceGetLink ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['URI_VIEW_ENTRY']))
+            return '';
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return '';
+
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function serviceGetTitle ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_TITLE', $iContentId);
+        return $mixedResult !== false ? $mixedResult : '';
+    }
+
+    public function serviceGetText ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_TEXT', $iContentId);
+        if (false === $mixedResult)
+            return '';
+
+        $CNF = &$this->_oConfig->CNF;
+        if (!empty($CNF['OBJECT_METATAGS']) && is_string($mixedResult)) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $mixedResult = $oMetatags->metaParse($iContentId, $mixedResult);
+        }
+
+        return $mixedResult;
+    }
+
+    public function serviceGetInfo ($iContentId, $bSearchableFieldsOnly = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_getFields($iContentId);
+        if(empty($aContentInfo))
+            return array();
+
+        if(!$bSearchableFieldsOnly)
+            return $aContentInfo;
+
+        if(empty($CNF['PARAM_SEARCHABLE_FIELDS']))
+            return array();
+
+        $aFields = explode(',', getParam($CNF['PARAM_SEARCHABLE_FIELDS']));
+        if(empty($aFields))
+            return array();
+
+        $aResult = array();
+        foreach($aFields as $sField)
+            if(isset($aContentInfo[$sField]))
+                $aResult[$sField] = $aContentInfo[$sField];
+
+        return $aResult;
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return '';
+
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit.html';
+
+        return $this->_oTemplate->unit($aContentInfo, true, $sUnitTemplate);
+    }
+
+    public function serviceGetAll ($aParams = array())
+    {
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array('type' => 'all');
+
+        return $this->_oDb->getEntriesBy($aParams);
+    }
+
+    public function serviceGetAllByAuthor ($iProfileId)
+    {
+        return $this->_oDb->getEntriesByAuthor((int)$iProfileId);
+    }
+
+    public function serviceGetSearchableFieldsExtended($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_FORM_ENTRY']))
+            return array();
+
+        $aResult = array();
+        if(!empty($CNF['FIELD_AUTHOR']) && !in_array($CNF['FIELD_AUTHOR'], $this->_aSearchableNamesExcept))
+            $aResult[$CNF['FIELD_AUTHOR']] = array(
+                'type' => 'text_auto', 
+                'caption' => $CNF['T']['form_field_author'],
+                'info' => '',
+            	'value' => '',
+                'values' => '',
+                'pass' => ''
+            );
+
+        $aInputs = array();
+        if(!empty($CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'])) {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+
+            $aInputs = array_merge($aInputs, $oForm->aInputs);
+        }
+        if(!empty($CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'])) {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'], $this->_oTemplate);
+
+            $aInputs = array_merge($aInputs, $oForm->aInputs);
+        }
+
+        if(!empty($aInputsAdd) && is_array($aInputsAdd))
+            $aInputs = array_merge($aInputs, $aInputsAdd);
+
+        foreach($aInputs as $aInput){
+            if(in_array($aInput['type'], BxDolSearchExtended::$SEARCHABLE_TYPES) && !in_array($aInput['name'], $this->_aSearchableNamesExcept)) {
+                $aField = array(
+                    'type' => $aInput['type'], 
+                    'caption_system' => $aInput['caption_system_src'],
+                    'caption' => $aInput['caption_src'],
+                    'info' => $aInput['info_src'],
+                    'value' => !empty($aInput['value']) ? $aInput['value'] : '',
+                    'values' => !empty($aInput['values_src']) ? $aInput['values_src'] : '',
+                    'pass' => !empty($aInput['db']['pass']) ? $aInput['db']['pass'] : '',
+                );
+
+                if(isset($aInput['search_type']))
+                    $aField['search_type'] = $aInput['search_type'];
+
+                $aResult[$aInput['name']] = $aField;
+            }
+        }
+        return $aResult;
+    }
+
+    public function serviceGetSearchResultExtended($aParams, $iStart = 0, $iPerPage = 0)
+    {
+        if(empty($aParams) || !is_array($aParams))
+            return array();
+
+        return $this->_oDb->getEntriesBy(array('type' => 'search_ids', 'search_params' => $aParams, 'start' => $iStart, 'per_page' => $iPerPage));
+    }
+
+    public function serviceModuleIcon ()
+    {
+        return isset($this->_oConfig->CNF['ICON']) ? $this->_oConfig->CNF['ICON'] : '';
+    }
+
+    public function serviceGetSearchableFields ($aInputsAdd = array())
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        if(!isset($CNF['PARAM_SEARCHABLE_FIELDS']) || !isset($CNF['OBJECT_FORM_ENTRY']) || !isset($CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']))
+            return array();
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+        if(!$oForm)
+            return array();
+
+        $aInputs = $oForm->aInputs;
+        if(!empty($aInputsAdd) && is_array($aInputsAdd))
+            $aInputs = array_merge($aInputs, $aInputsAdd);
+
+        $aTextTypes = array('text', 'textarea');
+        $aTextFields = array();
+        foreach($aInputs as $r)
+            if(in_array($r['type'], $aTextTypes))
+                $aTextFields[$r['name']] = $r['caption'];
+
+        return $aTextFields;
+    }
+    
+    public function serviceManageTools($sType = 'common')
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->getGridObject($sType));
+        if(!$oGrid)
+            return '';
+
+		$CNF = &$this->_oConfig->CNF;
+
+		$sMenu = '';
+		if(BxDolAcl::getInstance()->isMemberLevelInSet(192)) {
+			$oPermalink = BxDolPermalinks::getInstance();
+
+			$aMenuItems = array();
+			if(!empty($CNF['OBJECT_GRID_COMMON']) && !empty($CNF['T']['menu_item_manage_my']))
+				$aMenuItems[] = array('id' => 'manage-common', 'name' => 'manage-common', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_COMMON']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_my']), 'active' => 1);
+			if(!empty($CNF['OBJECT_GRID_ADMINISTRATION']) && !empty($CNF['T']['menu_item_manage_all']))
+				$aMenuItems[] = array('id' => 'manage-administration', 'name' => 'manage-administration', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_ADMINISTRATION']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_all']), 'active' => 1);
+
+			if(count($aMenuItems) > 1) {
+	            $oMenu = new BxTemplMenu(array(
+	            	'template' => 'menu_vertical.html', 
+	            	'menu_items' => $aMenuItems
+	            ), $this->_oTemplate);
+	            $oMenu->setSelected($this->_aModule['name'], 'manage-' . $sType);
+	            $sMenu = $oMenu->getCode();
+			}
+		}
+
+		if(!empty($CNF['OBJECT_MENU_SUBMENU']) && isset($CNF['URI_MANAGE_COMMON'])) {
+			BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SUBMENU'])->setSelected($this->_aModule['name'], $CNF['URI_MANAGE_COMMON']);
+		}
+
+        $this->_oTemplate->addCss(array('manage_tools.css'));
+        $this->_oTemplate->addJs(array('manage_tools.js'));
+        $this->_oTemplate->addJsTranslation(array('_sys_grid_search'));
+        return array(
+        	'content' => $this->_oTemplate->getJsCode('manage_tools', array('sObjNameGrid' => $this->_oConfig->getGridObject($sType))) . $oGrid->getCode(),
+        	'menu' => $sMenu
+        );
+    }
+
+    public function serviceGetMenuAddonManageTools()
+    {
+    	return 0;
+    }
+    
+    public function serviceGetMenuAddonManageToolsProfileStats()
+    {
+    	return 0;
+    }
+
+    /**
+     * My entries actions block
+     */
+    public function serviceMyEntriesActions ($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']))
+            return false;
+
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId || !($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return false;
+
+        if ($iProfileId != $this->_iProfileId)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * Universal browse method
+     * @param $aParams custom browse params, possible params are the following:
+     *  - mode - browse mode, such as 'recent', 'featured', etc
+     *  - params - custom params to browse method, for example 'unit_view' can be passed here
+     *  - design_box - design box style, @see BxBaseFunctions::DesignBoxContent 
+     *  - empty_message - display or not "empty" message when there is no content
+     *  - ajax_paginate - use AJAX paginate or not
+     *  @return HTML string
+     */
+    public function serviceBrowse ($aParams = array())
+    {
+        $aDefaults = array (
+            'mode' => 'recent',
+            'params' => false,
+            'design_box' => BX_DB_PADDING_DEF,
+            'empty_message' => false,
+            'ajax_paginate' => true,
+        );
+        $aParams = array_merge($aDefaults, $aParams);
+        return $this->_serviceBrowse ($aParams['mode'], $aParams['params'], $aParams['design_box'], $aParams['empty_message'], $aParams['ajax_paginate']);
+    }
+    
+	/**
+     * Display featured entries
+     * @return HTML string
+     */
+    public function serviceBrowseFeatured ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('featured', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+	
+	/**
+     * Display entries favored by a member
+     * @return HTML string
+     */
+    public function serviceBrowseFavorite ($iProfileId = 0, $aParams = array())
+    {
+        $oProfile = null;
+        if((int)$iProfileId)
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile && bx_get('profile_id') !== false)
+            $oProfile = BxDolProfile:: getInstance(bx_process_input(bx_get('profile_id'), BX_DATA_INT));
+        if(!$oProfile)
+            $oProfile = BxDolProfile::getInstance();
+        if(!$oProfile)
+            return '';
+
+        $bEmptyMessage = false;
+        if(isset($aParams['empty_message'])) {
+            $bEmptyMessage = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        return $this->_serviceBrowse ('favorite', array_merge(array('user' => $oProfile->id()), $aParams), BX_DB_PADDING_DEF, $bEmptyMessage);
+    }
+
+    public function serviceFormsHelper ()
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        return new $sClass($this);
+    }
+
+	/**
+     * Add entry using provided fields' values.
+     * @return array with result: 'code' is 0 on success or non-zero on error, 'message' is error message in case of error, 'content' is content info array in case of success
+     */
+    public function serviceEntityAdd ($iProfile, $aValues)
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->addData($iProfile, $aValues);
+    }
+
+	/**
+     * Perform redirect after content creation
+     * @return nothing, rediret header is sent
+     */    
+    public function serviceRedirectAfterAdd($aContentInfo)
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        $oFormsHelper->redirectAfterAdd($aContentInfo);
+    }
+
+    /**
+     * Get form object for add, edit, view or delete the content 
+     * @param $sType 'add', 'edit', 'view' or 'delete'
+     * @param $aParams optional array with parameters(display name, etc)
+     * @return form object or false on error
+     */
+    public function serviceGetObjectForm ($sType, $aParams = array())
+    {
+        if(!in_array($sType, array('add', 'edit', 'view', 'delete')))
+            return false;
+
+        $mixedContextId = isset($aParams['context_id']) ? $aParams['context_id'] : false;
+
+        $bContext = $mixedContextId !== false;
+        if($bContext && ($oContextProfile = BxDolProfile::getInstance(abs($mixedContextId))) !== false)
+            if($oContextProfile->checkAllowedPostInProfile() !== CHECK_ACTION_RESULT_ALLOWED)
+                return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+
+        $sParamsKey = 'ajax_mode';
+        if(isset($aParams[$sParamsKey]) && (bool)$aParams[$sParamsKey] === true)
+            $oFormsHelper->setAjaxMode((bool)$aParams[$sParamsKey]);
+
+        $sParamsKey = 'absolute_action_url';
+        if(isset($aParams[$sParamsKey]) && (bool)$aParams[$sParamsKey] === true)
+            $oFormsHelper->setAbsoluteActionUrl((bool)$aParams[$sParamsKey]);
+
+        $sDisplay = !empty($aParams['display']) ? $aParams['display'] : false;
+
+        $sFunc = 'getObjectForm' . ucfirst($sType);
+        $oForm = $oFormsHelper->$sFunc($sDisplay);
+
+        $sKey = 'FIELD_ALLOW_VIEW_TO';
+        if(!empty($CNF[$sKey]) && !empty($oForm->aInputs[$CNF[$sKey]]) && (!$bContext || $mixedContextId < 0)) {
+            $bContextOwner = $bContext && abs($mixedContextId) == (int)bx_get_logged_profile_id();
+
+            if(!$bContext || $bContextOwner) {
+                $iGc = 0;
+                $iKeyGh = false;
+                foreach($oForm->aInputs[$CNF[$sKey]]['values'] as $iKey => $aValue) {
+                    if(isset($aValue['type']) && in_array($aValue['type'], array('group_header', 'group_end'))) {
+                        if($iKeyGh !== false && $iGc == 0) {
+                            unset($oForm->aInputs[$CNF[$sKey]]['values'][$iKeyGh]);
+                            $iKeyGh = false;
+
+                            if($aValue['type'] == 'group_end')
+                                unset($oForm->aInputs[$CNF[$sKey]]['values'][$iKey]);
+                        }
+
+                        if($aValue['type'] == 'group_header') {
+                            $iGc = 0;
+                            $iKeyGh = $iKey;
+                        }
+
+                        continue;
+                    }
+
+                    //--- Show 'Public' privacy group only in Public post form. 
+                    if(!$bContext && $aValue['key'] == BX_DOL_PG_ALL) {
+                        $iGc += 1;
+                        continue;
+                    }
+
+                    //--- Show a default privacy groups in Profile (for Owner) post form.
+                    if($bContextOwner && (int)$aValue['key'] >= 0) {
+                        $iGc += 1;
+                        continue;
+                    }
+
+                    unset($oForm->aInputs[$CNF[$sKey]]['values'][$iKey]);
+                }
+            }
+            else {
+                $oForm->aInputs[$CNF[$sKey]]['value'] = $mixedContextId;
+                $oForm->aInputs[$CNF[$sKey]]['type'] = 'hidden';
+            }
+        }
+
+        bx_alert('system', 'get_object_form', 0, 0, array(
+            'module' => $this->_oConfig->getName(),
+            'type' => $sType,
+            'params' => $aParams,
+            'form' => &$oForm
+        ));
+
+        return $oForm;
+    }
+
+    /**
+     * Create entry form
+     * @return HTML string
+     */
+    public function serviceEntityCreate ($sParams = false)
+    {
+        $bParamsArray = is_array($sParams);
+
+        $sDisplay = is_string($sParams) ? $sParams : false;
+        if($bParamsArray && !empty($sParams['display']))
+            $sDisplay = $sParams['display'];
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        if($bParamsArray && isset($sParams['dynamic_mode']))
+            $oFormsHelper->setDynamicMode($sParams['dynamic_mode']);
+
+        $mixedResult = $oFormsHelper->addDataForm($sDisplay);
+        if(isset($mixedResult['_dt']) && $mixedResult['_dt'] == 'json') {
+            echoJson($mixedResult);
+            exit;
+        }
+
+        return $mixedResult;
+    }
+
+    public function serviceEntityEdit ($iContentId = 0, $sDisplay = false)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $sDisplay);
+    }
+
+    public function serviceEntityDelete ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('deleteDataForm', $iContentId);
+    }
+
+    public function serviceEntityTextBlock ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('viewDataEntry', $iContentId);
+    }
+
+    public function serviceEntityInfo ($iContentId = 0, $sDisplay = false)
+    {
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId, $sDisplay);
+    }
+
+	public function serviceEntityInfoFull ($iContentId = 0)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sDisplayName = 'OBJECT_FORM_ENTRY_DISPLAY_VIEW_FULL';
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId, !empty($CNF[$sDisplayName]) ? $CNF[$sDisplayName] : false);
+    }
+
+	public function serviceEntityInfoExtended ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryInfo', $iContentId);
+    }
+
+    /**
+     * Entry location info
+     */
+    public function serviceEntityLocation ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        return $this->_oTemplate->entryLocation ($iContentId);
+    }
+
+    /**
+     * Entry comments
+     */
+    public function serviceEntityComments ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_COMMENTS']))
+            return '';
+
+        return $this->_entityComments($CNF['OBJECT_COMMENTS'], $iContentId);
+    }
+
+    /**
+     * Entry attachments block
+     */
+    public function serviceEntityAttachments ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryAttachments', $iContentId);
+    }
+    
+    /**
+     * Delete content entry
+     * @param $iContentId content id 
+     * @return error message or empty string on success
+     */
+    public function serviceDeleteEntity ($iContentId, $sFuncDelete = 'deleteData')
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_oConfig->getClassPrefix() . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFuncDelete($iContentId);
+    }
+
+    /**
+     * 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;
+
+        $sObjectMenu = !empty($aParams['object_menu']) ? $aParams['object_menu'] : '';
+        if(empty($sObjectMenu) && !empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']))
+            $sObjectMenu = $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL'];
+
+        if(empty($sObjectMenu))
+            return false;
+
+        $sEntryTitle = !empty($aParams['entry_title']) ? $aParams['entry_title'] : '';
+        if(empty($sEntryTitle) && !empty($CNF['FIELD_TITLE']) && !empty($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sEntryTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+
+        $sEntryUrl = !empty($aParams['entry_url']) ? $aParams['entry_url'] : '';
+        if(empty($sEntryUrl) && !empty($CNF['URI_VIEW_ENTRY']))
+            $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId);
+
+        $iEntryThumb = !empty($aParams['entry_thumb']) ? (int)$aParams['entry_thumb'] : 0;
+        if(empty($iEntryThumb) && !empty($CNF['FIELD_THUMB']) && !empty($aContentInfo[$CNF['FIELD_THUMB']]))
+            $iEntryThumb = (int)$aContentInfo[$CNF['FIELD_THUMB']];
+
+        $sObjectStorage = !empty($aParams['object_storage']) ? $aParams['object_storage'] : false;
+        if(empty($sObjectStorage) && !empty($CNF['OBJECT_STORAGE']))
+            $sObjectStorage = $CNF['OBJECT_STORAGE'];
+
+        $sObjectTranscoder = !empty($aParams['object_transcoder']) ? $aParams['object_transcoder'] : false;
+
+        $aMarkers = array(
+            'id' => $iContentId,
+            'module' => $this->_oConfig->getName(),
+            'title' => !empty($sEntryTitle) ? $sEntryTitle : '',
+            'url' => !empty($sEntryUrl) ? $sEntryUrl : '',
+            'img_url' => ''
+        );
+
+        if(!empty($iEntryThumb)) {
+            if(!empty($sObjectTranscoder))
+                $o = BxDolTranscoder::getObjectInstance($sObjectTranscoder);
+            else if(!empty($sObjectStorage))
+                $o = BxDolStorage::getObjectInstance($sObjectStorage);
+
+            $sImageUrl = $o ? $o->getFileUrlById($iEntryThumb) : '';
+            if(!empty($sImageUrl))
+                $aMarkers['img_url'] = $sImageUrl;
+        }
+
+        $oActions = BxDolMenu::getObjectInstance($sObjectMenu);
+        if(!$oActions)
+            return false;
+
+        $oActions->setContentId($iContentId);
+        $oActions->addMarkers($aMarkers);
+        return $this->_oTemplate->entryAllActions($oActions->getCode());
+    }
+
+    /**
+     * Entry actions block
+     */
+    public function serviceEntityActions ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * Entry social sharing block.
+     * @param type $mixedContent integer content ID or array with integer content ID and array with content info, or false.
+     * @param type $aParams array with additional custom params which may overwrite some default values.
+     * @return boolean|string string with block content or false if something went wrong.
+     */
+    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;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $sUri = !empty($aParams['uri']) ? $aParams['uri'] : '';
+        if(empty($sUri) && !empty($CNF['URI_VIEW_ENTRY']))
+            $sUri = $CNF['URI_VIEW_ENTRY'];
+
+        $sUrl = !empty($sUri) ? BxDolPermalinks::getInstance()->permalink('page.php?i=' . $sUri . '&id=' . $iContentId) : '';
+
+        $sTitle = !empty($aParams['title']) ? $aParams['title'] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+
+        $aMarkers = array(
+            'id' => $iContentId,
+            'module' => $this->_aModule['name'],
+            'url' => BX_DOL_URL_ROOT . $sUrl,
+            'title' => $sTitle,
+        );
+
+        $iIdThumb = !empty($aParams['id_thumb']) ? (int)$aParams['id_thumb'] : 0;
+        if(empty($iIdThumb) && !empty($CNF['FIELD_THUMB']) && !empty($aContentInfo[$CNF['FIELD_THUMB']]))
+            $iIdThumb = (int)$aContentInfo[$CNF['FIELD_THUMB']];
+
+        if ($iIdThumb) {
+            $sTranscoder = !empty($aParams['object_transcoder']) ? $aParams['object_transcoder'] : '';
+            $sStorage = !empty($aParams['object_storage']) ? $aParams['object_storage'] : '';
+            if(empty($sStorage) && !empty($CNF['OBJECT_STORAGE']))
+                $sStorage = $CNF['OBJECT_STORAGE'];
+
+            if(!empty($sTranscoder))
+                $o = BxDolTranscoder::getObjectInstance($sTranscoder);
+            else if(!empty($sStorage))
+                $o = BxDolStorage::getObjectInstance($sStorage);
+
+            $sImgUrl = $o ? $o->getFileUrlById($iIdThumb) : '';
+            if($sImgUrl)
+                $aMarkers['img_url'] = $sImgUrl;
+        }
+
+        $oMenu = BxDolMenu::getObjectInstance('sys_social_sharing');
+        $oMenu->addMarkers($aMarkers);
+        $sMenu = $oMenu->getCode();
+
+        if(empty($sMenu))
+            return '';
+
+        return $this->_oTemplate->parseHtmlByName('entry-share.html', array(
+            'menu' => $sMenu
+        ));
+    }
+
+    /**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+    	$sEventPrivacy = $sModule . '_allow_view_event_to';
+        if(BxDolPrivacy::getObjectInstance($sEventPrivacy) === false)
+            $sEventPrivacy = '';
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_notifications_post', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
+                array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted'),
+
+                array('group' => $sModule . '_comment', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'commentPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_comment', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_comment', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'commentRemoved'),
+
+                array('group' => $sModule . '_reply', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'replyPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_reply', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_reply', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'replyRemoved'),
+
+                array('group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_vote', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'undoVote'),
+
+                array('group' => $sModule . '_reaction', 'type' => 'insert', 'alert_unit' => $sModule . '_reactions', 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_reaction', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_reaction', 'type' => 'delete', 'alert_unit' => $sModule . '_reactions', 'alert_action' => 'undoVote'),
+
+                array('group' => $sModule . '_score_up', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteUp', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_up', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+
+                array('group' => $sModule . '_score_down', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVoteDown', 'module_name' => $sModule, 'module_method' => 'get_notifications_score_down', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+            ),
+            'settings' => array(
+                array('group' => 'content', 'unit' => $sModule, 'action' => 'added', 'types' => array('follow_member', 'follow_context')),
+                array('group' => 'comment', 'unit' => $sModule, 'action' => 'commentPost', 'types' => array('personal', 'follow_member', 'follow_context')),
+                array('group' => 'reply', 'unit' => $sModule, 'action' => 'replyPost', 'types' => array('personal')),
+                array('group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => array('personal', 'follow_member', 'follow_context')),
+                array('group' => 'vote', 'unit' => $sModule . '_reactions', 'action' => 'doVote', 'types' => array('personal', 'follow_member', 'follow_context')),
+                array('group' => 'score_up', 'unit' => $sModule, 'action' => 'doVoteUp', 'types' => array('personal', 'follow_member', 'follow_context')),
+                array('group' => 'score_down', 'unit' => $sModule, 'action' => 'doVoteDown', 'types' => array('personal', 'follow_member', 'follow_context'))
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'added'),
+                array('unit' => $sModule, 'action' => 'edited'),
+                array('unit' => $sModule, 'action' => 'deleted'),
+
+                array('unit' => $sModule, 'action' => 'commentPost'),
+                array('unit' => $sModule, 'action' => 'commentRemoved'),
+
+                array('unit' => $sModule, 'action' => 'replyPost'),
+                array('unit' => $sModule, 'action' => 'replyRemoved'),
+
+                array('unit' => $sModule, 'action' => 'doVote'),
+                array('unit' => $sModule, 'action' => 'undoVote'),
+
+                array('unit' => $sModule . '_reactions', 'action' => 'doVote'),
+                array('unit' => $sModule . '_reactions', 'action' => 'undoVote'),
+
+                array('unit' => $sModule, 'action' => 'doVoteUp'),
+                array('unit' => $sModule, 'action' => 'doVoteDown'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Notifications module
+     */
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'entry_privacy' => '', //may be empty or not specified. In this case Public privacy will be used.
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+            /*
+             * Custom settings for email and/or push notifications can be provided here. 
+             * Only necessary parts of 'settings' array can be used.
+             * 
+            'settings' => array(
+                'email' => array(
+                    'template' => '',   //--- custom email template
+                    'markers' => '',    //--- markers to parse email parts (subject, body) with
+                    'subject' => ''     //--- custom email subject
+                ),
+                'push' => array(
+                    'subject' => ''     //--- custom push notification subject
+                )
+            )
+             */
+        );
+    }
+
+	/**
+     * Entry post comment for Notifications module
+     */
+    public function serviceGetNotificationsComment($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+		$oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $iContentId);
+        if(!$oComment || !$oComment->isEnabled())
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+		return array(
+			'entry_sample' => $CNF['T']['txt_sample_single'],
+			'entry_url' => $sEntryUrl,
+			'entry_caption' => $sEntryCaption,
+			'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+			'subentry_sample' => $CNF['T']['txt_sample_comment_single'],
+			'subentry_url' => $oComment->getViewUrl((int)$aEvent['subobject_id']),
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+	/**
+     * Entry post reply for Notifications module
+     */
+    public function serviceGetNotificationsReply($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], 0, false);
+        if(!$oComment || !$oComment->isEnabled())
+            return array();
+
+    	$iParentId = (int)$aEvent['object_id'];
+        $aParentInfo = $oComment->getQueryObject()->getCommentsBy(array('type' => 'id', 'id' => $iParentId));
+        if(empty($aParentInfo) || !is_array($aParentInfo))
+            return array();
+
+        $iObjectId = (int)$aParentInfo['cmt_object_id'];
+		$oComment->init($iObjectId);
+
+		return array(
+		    'object_id' => $iObjectId,
+			'entry_sample' => '_cmt_txt_sample_comment_single',
+			'entry_url' => $oComment->getViewUrl($iParentId),
+			'entry_caption' => strmaxtextlen($aParentInfo['cmt_text'], 20, '...'),
+			'entry_author' => (int)$aParentInfo['cmt_author_id'],
+			'subentry_sample' => '_cmt_txt_sample_reply_to',
+			'subentry_url' => $oComment->getViewUrl((int)$aEvent['subobject_id']),
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+	/**
+     * Entry post vote for Notifications module
+     */
+    public function serviceGetNotificationsVote($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $iContentId);
+        if(!$oVote || !$oVote->isEnabled())
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => $CNF['T']['txt_sample_vote_single'],
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    /**
+     * Entry post vote for Notifications module
+     */
+    public function serviceGetNotificationsReaction($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oReaction = BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], $iContentId);
+        if(!$oReaction || !$oReaction->isEnabled())
+            return array();
+
+        $aSubentry = $oReaction->getTrackBy(array('type' => 'id', 'id' => (int)$aEvent['subobject_id']));
+        if(empty($aSubentry) || !is_array($aSubentry))
+            return array();
+
+        $aSubentrySampleParams = array();
+        $aReaction = $oReaction->getReaction($aSubentry['reaction']);
+        if(!empty($aReaction['title']))
+            $aSubentrySampleParams[] = $aReaction['title'];
+        else
+            $aSubentrySampleParams[] = '_undefined';
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => $CNF['T']['txt_sample_reaction_single'],
+            'subentry_sample_params' => $aSubentrySampleParams,
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+
+    /**
+     * Entry post score -> vote up for Notifications module
+     */
+    public function serviceGetNotificationsScoreUp($aEvent)
+    {
+    	return $this->_serviceGetNotificationsScore('up', $aEvent);
+    }
+
+    /**
+     * Entry post score -> vote up for Notifications module
+     */
+    public function serviceGetNotificationsScoreDown($aEvent)
+    {
+    	return $this->_serviceGetNotificationsScore('down', $aEvent);
+    }
+
+    protected function _serviceGetNotificationsScore($sType, $aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oScore = BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $iContentId);
+        if(!$oScore || !$oScore->isEnabled())
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $sEntryCaption,
+            'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'subentry_sample' => $CNF['T']['txt_sample_score_' . $sType . '_single'],
+            'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+        );
+    }
+    
+    /**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_timeline_post', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
+                array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted')
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'added'),
+                array('unit' => $sModule, 'action' => 'edited'),
+                array('unit' => $sModule, 'action' => 'deleted'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $iUserId = $this->getUserId();
+        $iAuthorId = (int)$aContentInfo[$CNF['FIELD_AUTHOR']];
+        $iAuthorIdAbs = abs($iAuthorId);
+        if($iAuthorId < 0 && ((is_numeric($aEvent['owner_id']) && $iAuthorIdAbs == (int)$aEvent['owner_id']) || (is_array($aEvent['owner_id']) && in_array($iAuthorIdAbs, $aEvent['owner_id']))) && $iAuthorIdAbs != $iUserId)
+            return false;
+
+        //--- Views
+        $oViews = isset($CNF['OBJECT_VIEWS']) ? BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $aEvent['object_id']) : null;
+
+        $aViews = array();
+        if ($oViews && $oViews->isEnabled())
+            $aViews = array(
+                'system' => $CNF['OBJECT_VIEWS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['views']
+            );
+
+        //--- Votes
+        $oVotes = isset($CNF['OBJECT_VOTES']) ? BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $aEvent['object_id']) : null;
+
+        $aVotes = array();
+        if ($oVotes && $oVotes->isEnabled())
+            $aVotes = array(
+                'system' => $CNF['OBJECT_VOTES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['votes']
+            );
+        
+        //--- Reactions
+        $oReactions = isset($CNF['OBJECT_REACTIONS']) ? BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], $aEvent['object_id']) : null;
+
+        $aReactions = array();
+        if ($oReactions && $oReactions->isEnabled())
+            $aReactions = array(
+                'system' => $CNF['OBJECT_REACTIONS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['rvotes']
+            );
+
+        //--- Scores
+        $oScores = isset($CNF['OBJECT_SCORES']) ? BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $aEvent['object_id']) : null;
+
+        $aScores = array();
+        if ($oScores && $oScores->isEnabled())
+            $aScores = array(
+                'system' => $CNF['OBJECT_SCORES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'score' => $aContentInfo['score']
+            );
+
+		//--- Reports
+        $oReports = isset($CNF['OBJECT_REPORTS']) ? BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS'], $aEvent['object_id']) : null;
+
+        $aReports = array();
+        if ($oReports && $oReports->isEnabled())
+            $aReports = array(
+                'system' => $CNF['OBJECT_REPORTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['reports']
+            );
+
+        //--- Comments
+        $oCmts = isset($CNF['OBJECT_COMMENTS']) ? BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $aEvent['object_id']) : null;
+
+        $aComments = array();
+        if($oCmts && $oCmts->isEnabled())
+            $aComments = array(
+                'system' => $CNF['OBJECT_COMMENTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['comments']
+            );
+
+        //--- 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']];
+
+        $iOwnerId = $iAuthorIdAbs;
+        if(isset($aEvent['object_privacy_view']) && (int)$aEvent['object_privacy_view'] < 0)
+            $iOwnerId = abs($aEvent['object_privacy_view']);
+
+        $bCache = true;
+        $aContent = $this->_getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams);
+        if(isset($aContent['_cache'])) {
+            $bCache = (bool)$aContent['_cache'];
+
+            unset($aContent['_cache']);
+        }
+
+        return array(
+            '_cache' => $bCache,
+            'owner_id' => $iOwnerId,
+            'object_owner_id' => $iAuthorId,
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+            'sample_action' => isset($CNF['T']['txt_sample_action']) ? $CNF['T']['txt_sample_action'] : '',
+            'url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'content' => $aContent, //a string to display or array to parse default template before displaying.
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => $aViews,
+            'votes' => $aVotes,
+            'reactions' => $aReactions,
+            'scores' => $aScores,
+            'reports' => $aReports,
+            'comments' => $aComments,
+            'title' => $sTitle, //may be empty.
+            'description' => '' //may be empty.
+        );
+    }
+
+    public function serviceGetTimelinePostAllowedView($aEvent)
+    {
+        $iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return _t('_sys_txt_access_denied');
+
+        return $this->serviceCheckAllowedViewForProfile($aContentInfo);
+    }
+
+    /**
+     * Check particular action permission without content
+     * @param $sAction action to check, for example: Browse, Add
+     * @param $iContentId content ID
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowed($sAction, $isPerformAction = false)
+    {
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction);
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($isPerformAction);
+    }
+    
+    /**
+     * Check particular action permission with content
+     * @param $sAction action to check, for example: View, Edit
+     * @param $iContentId content ID
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowedWithContent($sAction, $iContentId, $isPerformAction = false)
+    {
+        if (!$iContentId || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)))
+            return _t('_sys_request_page_not_found_cpt');
+
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction);
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($aContentInfo, $isPerformAction);
+    }
+    
+    public function serviceCheckAllowedCommentsView($iContentId, $sObjectComments) 
+    {
+        return $this->serviceCheckAllowedWithContent('comments_view', $iContentId);
+    }
+    
+    public function serviceCheckAllowedCommentsPost($iContentId, $sObjectComments) 
+    {
+        return $this->serviceCheckAllowedWithContent('comments_post', $iContentId);
+    }
+
+    public function serviceGetContentOwnerProfileId ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // file owner must be author of the content or profile itself in case of profile based module
+        if ($iContentId) {
+            $sModule = $this->getName();
+            if ($this instanceof iBxDolProfileService && BxDolService::call($sModule, 'act_as_profile')) {
+                $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $sModule);
+            }
+            else {
+                $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                $oProfile = $aContentInfo ? BxDolProfile::getInstance($aContentInfo[$CNF['FIELD_AUTHOR']]) : null;
+            }
+            $iProfileId = $oProfile ? $oProfile->id() : bx_get_logged_profile_id();
+        }
+        else {
+            $iProfileId = bx_get_logged_profile_id();
+        }
+
+        return $iProfileId;
+    }
+
+
+    // ====== PERMISSION METHODS
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        // moderator and owner always have access
+        if (!empty($iProfileId) && (abs($aDataEntry[$CNF['FIELD_AUTHOR']]) == (int)$iProfileId || $this->_isModerator($isPerformAction)))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($iProfileId, 'view entry', $this->getName(), $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        // check privacy
+        if (!empty($CNF['OBJECT_PRIVACY_VIEW'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+            if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_view', 0, 0, array('module' => $this->getName(), 'content_info' => $aDataEntry, 'profile_id' => $iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedSetThumb ($iContentId = 0)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedBrowse ()
+    {
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_browse', 0, 0, array('module' => $this->getName(), 'profile_id' => $this->_iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedAdd ($isPerformAction = false)
+    {
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'create entry', $this->getName(), $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @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 checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator and owner always have access
+        if ($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
+            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. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
+        if (($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId) && $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. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedSetMembership (&$aDataEntry, $isPerformAction = false)
+    {
+        // admin always has access
+        if (isAdmin())
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'set acl level', 'system', $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @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 checkAllowedEditAnyEntry ($isPerformAction = false)
+    {
+    	$aCheck = checkActionModule($this->_iProfileId, 'edit any entry', $this->getName(), $isPerformAction);
+    	if($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. So make sure to make "true === " checking.
+     */
+    public function checkAllowedCommentsView ($aContentInfo, $isPerformAction = false)
+    {
+        return $this->checkAllowedView ($aContentInfo, $isPerformAction);
+    }
+
+	/**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedCommentsPost ($aContentInfo, $isPerformAction = false)
+    {
+        return $this->checkAllowedView ($aContentInfo, $isPerformAction);
+    }
+
+    public function _serviceBrowse ($sMode, $aParams = false, $iDesignBox = BX_DB_PADDING_DEF, $bDisplayEmptyMsg = false, $bAjaxPaginate = true, $sClassSearchResult = 'SearchResult')
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse()))
+            return MsgBox($sMsg);
+
+        bx_import($sClassSearchResult, $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . $sClassSearchResult;
+        $o = new $sClass($sMode, $aParams);
+
+        $o->setDesignBoxTemplateId($iDesignBox);
+        $o->setDisplayEmptyMsg($bDisplayEmptyMsg);
+        $o->setAjaxPaginate($bAjaxPaginate);
+        $o->setUnitParams(array('context' => $sMode));
+
+        if ($o->isError)
+            return '';
+
+        if ($s = $o->processing())
+            return $s;
+        else
+            return '';
+    }
+
+    // ====== COMMON METHODS
+    public function alertAfterAdd($aContentInfo) {}
+    
+    public function alertAfterEdit($aContentInfo) {}
+
+    public function onPublished($iContentId) {}
+
+    public function onFailed($iContentId) {}
+
+    public function processMetasAdd($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_METATAGS'])) 
+            return;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        $bFldStatus = isset($CNF['FIELD_STATUS']);
+        $bFldStatusAdmin = isset($CNF['FIELD_STATUS_ADMIN']);
+        $bContentInfo = $aContentInfo && (!$bFldStatus || ($bFldStatus && $aContentInfo[$CNF['FIELD_STATUS']] == 'active')) && (!$bFldStatusAdmin || ($bFldStatusAdmin && $aContentInfo[$CNF['FIELD_STATUS_ADMIN']] == 'active'));
+        if(!$bContentInfo)
+            return;
+
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $oMetatags->metaAddAuto($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']);
+
+        if($oMetatags->locationsIsEnabled())
+            $oMetatags->locationsAddFromForm($aContentInfo[$CNF['FIELD_ID']], $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);
+    }
+    
+    public function processMetasEdit($iContentId, $oForm)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_METATAGS']))
+            return;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+
+        $bFldStatus = isset($CNF['FIELD_STATUS']);
+        $bFldStatusAdmin = isset($CNF['FIELD_STATUS_ADMIN']);
+        $bContentInfo = $aContentInfo && (!$bFldStatus || ($bFldStatus && $aContentInfo[$CNF['FIELD_STATUS']] == 'active')) && (!$bFldStatusAdmin || ($bFldStatusAdmin && $aContentInfo[$CNF['FIELD_STATUS_ADMIN']] == 'active'));
+        if(!$bContentInfo)
+            return;
+
+        $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);
+    }
+
+    public function getEntryImageData($aContentInfo, $sField = 'FIELD_THUMB', $aTranscoders = array())
+    {
+        if(empty($aTranscoders))
+            $aTranscoders = array('OBJECT_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_GALLERY');
+
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF[$sField]) || empty($aContentInfo[$CNF[$sField]]) || empty($CNF['OBJECT_STORAGE']))
+            return false;
+
+        $iId = (int)$aContentInfo[$CNF[$sField]];
+        foreach($aTranscoders as $sTranscoder)
+            if(!empty($CNF[$sTranscoder]))
+                return array('id' => $iId, 'transcoder' => $CNF[$sTranscoder]);
+
+        return array('id' => $iId, 'object' => $CNF['OBJECT_STORAGE']);
+    }
+
+    public function getProfileId()
+    {
+    	return bx_get_logged_profile_id();
+    }
+
+	public function getProfileInfo($iUserId = 0)
+    {
+        $oProfile = $this->getObjectUser($iUserId);
+
+        $oAccount = null;
+        if($oProfile && !($oProfile instanceof BxDolProfileUndefined) && !($oProfile instanceof BxDolProfileAnonymous))
+            $oAccount = $oProfile->getAccountObject();
+        $bAccount = !empty($oAccount);
+
+        return array(
+        	'id' => $oProfile->id(),
+            'name' => $oProfile->getDisplayName(),
+        	'email' => $bAccount ? $oAccount->getEmail() : '',
+            'link' => $oProfile->getUrl(),
+            'icon' => $oProfile->getIcon(),
+        	'thumb' => $oProfile->getThumb(),
+        	'avatar' => $oProfile->getAvatar(),
+            'unit' => $oProfile->getUnit(),
+        	'active' => $oProfile->isActive(),
+        );
+    }
+    
+    public function getObjectUser($iUserId = 0)
+    {
+    	bx_import('BxDolProfile');
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+        return $oProfile;
+    }
+
+    public function getObjectFavorite($sSystem = '', $iId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($sSystem) && !empty($CNF['OBJECT_FAVORITES']))
+            $sSystem = $CNF['OBJECT_FAVORITES'];
+
+        if(empty($sSystem))
+            return false;
+
+        $oFavorite = BxDolFavorite::getObjectInstance($sSystem, $iId, true, $this->_oTemplate);
+        if(!$oFavorite || !$oFavorite->isEnabled())
+            return false;
+
+        return $oFavorite;
+    }
+
+	public function getUserId()
+    {
+        return isLogged() ? bx_get_logged_profile_id() : 0;
+    }
+
+    public function getUserIp()
+    {
+        return getVisitorIP();
+    }
+
+    public function getUserInfo($iUserId = 0)
+    {
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit(),
+            $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+        );
+    }
+
+    public function isMenuItemVisible($sObject, &$aItem, &$aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // default visible settings
+        if(!BxDolAcl::getInstance()->isMemberLevelInSet($aItem['visible_for_levels']))
+            return false;
+
+        if (!empty($aItem['visibility_custom'])) {
+            $oMenu = BxDolMenu::getObjectInstance($sObject);
+            if ($oMenu && !BxDolService::callSerialized($aItem['visibility_custom'], $oMenu->getMarkers()))
+                return false;
+        }
+        
+        // get custom function name to check menu item visibility
+        $sFuncCheckAccess = false;
+        if(isset($CNF['MENU_ITEM_TO_METHOD'][$sObject][$aItem['name']]))
+            $sFuncCheckAccess = $CNF['MENU_ITEM_TO_METHOD'][$sObject][$aItem['name']];
+
+        // check custom visibility settings defined in module config class
+        if(!isset($aContentInfo))
+            $aContentInfo = array();
+
+        if($sFuncCheckAccess && CHECK_ACTION_RESULT_ALLOWED !== call_user_func_array(array($this, $sFuncCheckAccess), array(&$aContentInfo)))
+            return false;
+
+        return true;
+    }
+
+    public function _isModerator ($isPerformAction = false)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED === $this->checkAllowedEditAnyEntry ($isPerformAction);
+    }
+
+    // ====== PROTECTED METHODS
+
+    protected function _serviceEntityForm ($sFormMethod, $iContentId = 0, $sDisplay = false, $sCheckFunction = false, $bErrorMsg = true)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFormMethod((int)$iContentId, $sDisplay, $sCheckFunction, $bErrorMsg);
+    }
+
+    protected function _serviceTemplateFunc ($sFunc, $iContentId, $sFuncGetContent = 'getContentInfoById')
+    {
+        $mixedContent = $this->_getContent($iContentId, $sFuncGetContent);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        return $this->_oTemplate->$sFunc($aContentInfo);
+    }
+
+    protected function _rss ($aArgs, $sClass = 'SearchResult')
+    {
+        $sMode = array_shift($aArgs);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse())) {
+            $this->_oTemplate->displayAccessDenied ($sMsg);
+            exit;
+        }
+
+        $aParams = $this->_buildRssParams($sMode, $aArgs);
+
+        bx_import ($sClass, $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . $sClass;
+        $o = new $sClass($sMode, $aParams);
+
+        if ($o->isError)
+            $this->_oTemplate->displayPageNotFound ();
+        else
+            $o->outputRSS();
+
+        exit;
+    }
+
+    protected function _getContent($iContentId = 0, $sFuncGetContent = true)
+    {
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iContentId)
+            return false;
+
+        if($sFuncGetContent === true)
+            $sFuncGetContent = 'getContentInfoById';
+
+        if(empty($sFuncGetContent) || !method_exists($this->_oDb, $sFuncGetContent))
+            return $iContentId;
+
+        $aContentInfo = $this->_oDb->$sFuncGetContent($iContentId);
+        if(!$aContentInfo)
+            return false;
+
+        return array($iContentId, $aContentInfo);
+    }
+
+    protected function _getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams = array())
+    {
+    	$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)
+        $aImages = $this->_getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+        $aImagesAttach = $this->_getImagesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Video(s)
+        $aVideos = $this->_getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+        $aVideosAttach = $this->_getVideosForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Files(s)
+        $aFiles = $this->_getFilesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+        $aFilesAttach = $this->_getFilesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Title
+        $sTitle = '';
+        if(isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+        else if(isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+        //--- Text
+        $sText = isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]) ? $aContentInfo[$CNF['FIELD_TEXT']] : '';
+        if(!empty($CNF['OBJECT_METATAGS']) && is_string($sText)) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $sText = $oMetatags->metaParse($aContentInfo[$CNF['FIELD_ID']], $sText);
+        }
+
+    	return array(
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+            'sample_action' => isset($CNF['T']['txt_sample_action']) ? $CNF['T']['txt_sample_action'] : '',
+            'url' => $sUrl,
+            'title' => $sTitle,
+            'text' => $sText,
+            'images' => $aImages,
+            'images_attach' => $aImagesAttach,
+            'videos' => $aVideos,
+            'videos_attach' => $aVideosAttach,
+            'files' => $aFiles,
+            'files_attach' => $aFilesAttach
+        );
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sImage = $sImageOrig = '';
+        if(isset($CNF['FIELD_COVER']) && isset($aContentInfo[$CNF['FIELD_COVER']]) && $aContentInfo[$CNF['FIELD_COVER']]) {
+            $sImage = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_COVER']], array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_THUMB'));
+            $sImageOrig = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_COVER']], array('OBJECT_IMAGES_TRANSCODER_COVER'));
+        }
+
+        $bImageThumb = isset($CNF['FIELD_THUMB']) && isset($aContentInfo[$CNF['FIELD_THUMB']]) && $aContentInfo[$CNF['FIELD_THUMB']];
+        if($sImage == '' && $bImageThumb)
+            $sImage = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_THUMB']], array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_THUMB'));
+
+        if($sImageOrig == '' && $bImageThumb)
+            $sImageOrig = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_THUMB']], array('OBJECT_IMAGES_TRANSCODER_COVER'));
+
+        if(empty($sImage))
+            return array();
+
+        if($sImageOrig == '')
+            $sImageOrig = $sImage;
+
+        return array(
+            array('url' => $sUrl, 'src' => $sImage, 'src_orig' => $sImageOrig),
+        );
+    }
+
+    protected function _getImagesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getVideosForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getFilesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _getFilesForTimelinePostAttach($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _entityComments ($sObject, $iId = 0)
+    {
+        if (!$iId)
+            $iId = bx_process_input(bx_get('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, 'show_empty' => true));
+    }
+
+    protected function _getFields($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return array();
+
+        return BxDolContentInfo::formatFields($aContentInfo);
+    }
+
+    protected function _getFieldValue($sField, $iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF[$sField]))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || empty($aContentInfo[$CNF[$sField]]))
+            return false;
+
+        return $aContentInfo[$CNF[$sField]];
+    }
+
+    protected function _getFieldValueThumb($sField, $iContentId, $sTranscoder = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sTranscoder) || empty($CNF[$sField]))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || empty($aContentInfo[$CNF[$sField]]))
+            return false;
+
+        $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($sTranscoder);
+        if(!$oImagesTranscoder)
+            return false;
+
+        return $oImagesTranscoder->getFileUrl($aContentInfo[$CNF[$sField]]);
+    }
+
+	protected function _prepareResponse($aResponse, $bAsJson = false, $aAdditional = array())
+    {
+    	if(!$bAsJson)
+    		return $aResponse;
+
+		if(!empty($aAdditional) && is_array($aAdditional))
+			$aResponse = array_merge($aResponse, $aAdditional);
+
+		echoJson($aResponse);
+		exit;
+    }
+}
+
+/** @} */

+ 228 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php

@@ -0,0 +1,228 @@
+<?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
+ *
+ * @{
+ */
+
+/**
+ * Profile forms helper functions
+ */
+class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
+{
+    protected $_bAutoApproval = false;
+
+    public function __construct($oModule)
+    {
+        parent::__construct($oModule);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bAutoApproval = isset($CNF['PARAM_AUTOAPPROVAL']) ? (bool)getParam($CNF['PARAM_AUTOAPPROVAL']) : true;
+        $bAdministrator = BxDolAcl::getInstance()->isMemberLevelInSet(array(MEMBERSHIP_ID_ADMINISTRATOR));
+        $this->setAutoApproval(isAdmin() || $bAdministrator || $bAutoApproval);
+    }
+
+    public function isAutoApproval()
+    {
+        return $this->_bAutoApproval;
+    }
+
+    public function setAutoApproval($b)
+    {
+        return ($this->_bAutoApproval = $b);
+    }
+
+    protected function _getProfileAndContentData ($iContentId)
+    {
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+        if(!$aContentInfo)
+            return array (false, false);
+
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo['profile_id']);
+        return array($oProfile, $aContentInfo);
+    }
+
+    public function deleteData ($iContentId, $aContentInfo = false, $oProfile = null, $oForm = null)
+    {
+        if (!$aContentInfo)
+            list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        // delete profile
+        $oProfile = BxDolProfile::getInstance($aContentInfo['profile_id']);
+        if (!$oProfile->delete())
+            return _t('_sys_txt_error_entry_delete');
+
+        return '';
+    }
+
+    public function deleteDataService ($iContentId, $aContentInfo = false, $oProfile = null, $oForm = null)
+    {
+        return parent::deleteData ($iContentId, $aContentInfo, $oProfile, $oForm);
+    }
+
+    public function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
+    {
+        if($s = parent::onDataDeleteAfter($iContentId, $aContentInfo, $oProfile))
+            return $s;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bActAsProfile = BxDolService::call($oProfile->getModule(), 'act_as_profile');
+        if(($oPrivacyView = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW'])) !== false && $bActAsProfile)
+            $oPrivacyView->deleteGroupCustomByProfileId($oProfile->id());
+
+        if(($oPrivacyPost = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false) {
+            $oPrivacyPost->deleteGroupCustomByContentId($iContentId);
+
+            if($bActAsProfile)
+                $oPrivacyPost->deleteGroupCustomByProfileId($oProfile->id());
+        }
+
+        return '';
+    }
+
+    public function onDataEditBefore ($iContentId, $aContentInfo, &$aTrackTextFieldsChanges, &$oProfile, &$oForm)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        $sStatus = $oProfile->getStatus();
+        if (!$this->isAutoApproval() && BX_PROFILE_STATUS_ACTIVE == $sStatus)
+            $aTrackTextFieldsChanges = array ();
+    }
+
+    public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
+    {
+        if ($s = parent::onDataEditAfter($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm))
+            return $s;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        /*
+         * Load updated data.
+         */
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+
+        // change profile to 'pending' only if profile is 'active'
+        $sStatus = $oProfile->getStatus();
+        if (!$this->isAutoApproval() && BX_PROFILE_STATUS_ACTIVE == $sStatus && !empty($aTrackTextFieldsChanges['changed_fields']))
+            $oProfile->disapprove(BX_PROFILE_ACTION_AUTO, 0, $this->_oModule->serviceActAsProfile());
+
+        // process uploaded files
+        if (isset($CNF['FIELD_PICTURE']))
+            $oForm->processFiles($CNF['FIELD_PICTURE'], $iContentId, false);
+        if (isset($CNF['FIELD_COVER']))
+            $oForm->processFiles($CNF['FIELD_COVER'], $iContentId, false);
+
+        if(isset($CNF['FIELD_ALLOW_POST_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
+            $oPrivacy->reassociateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]);
+
+        return '';
+    }
+
+    public function onDataAddAfter ($iAccountId, $iContentId)
+    {
+        /*
+         * Add account and content association.
+         * Note. It should be done first to correctly get and use author's profile later.
+         */
+        $iProfileId = BxDolProfile::add(BX_PROFILE_ACTION_MANUAL, $iAccountId, $iContentId, BX_PROFILE_STATUS_PENDING, $this->_oModule->getName());
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+
+        if($s = parent::onDataAddAfter($iAccountId, $iContentId))
+            return $s;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        /*
+         * Load updated data.
+         */
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+
+        // approve profile if auto-approval is enabled and profile status is 'pending'
+        $sStatus = $oProfile->getStatus();
+        if ($sStatus == BX_PROFILE_STATUS_PENDING && $this->isAutoApproval())
+            $oProfile->approve(BX_PROFILE_ACTION_AUTO, 0, $this->_oModule->serviceActAsProfile() && $this->_oModule->serviceIsEnableProfileActivationLetter());
+
+        // set created profile some default membership
+        if ((int)bx_get('level_id') && bx_srv('bx_acl', 'get_prices', [(int)bx_get('level_id'), true]))
+            $iAclLevel = (int)bx_get('level_id');
+        else
+            $iAclLevel = !isset($CNF['PARAM_DEFAULT_ACL_LEVEL']) ? MEMBERSHIP_ID_STANDARD : 
+               (isAdmin() ? MEMBERSHIP_ID_ADMINISTRATOR : getParam($CNF['PARAM_DEFAULT_ACL_LEVEL']));
+        BxDolAcl::getInstance()->setMembership($iProfileId, $iAclLevel, 0, true);
+        
+        // process uploaded files
+        $oForm = $this->getObjectFormAdd();
+        if (isset($CNF['FIELD_PICTURE']))
+            $oForm->processFiles($CNF['FIELD_PICTURE'], $iContentId, true);
+        if (isset($CNF['FIELD_COVER']))
+            $oForm->processFiles($CNF['FIELD_COVER'], $iContentId, true);
+
+        if(isset($CNF['FIELD_ALLOW_POST_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
+            $oPrivacy->associateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]);
+
+        // switch context to the created profile
+        if ($this->_oModule->serviceActAsProfile()) {
+            $oAccount = BxDolAccount::getInstance($iAccountId);
+            $oAccount->updateProfileContext($iProfileId);
+        }
+
+        return '';
+    }
+
+    public function redirectAfterAdd($aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oSession = BxDolSession::getInstance();
+
+        $sRedirectType = empty($CNF['PARAM_REDIRECT_AADD']) ? BX_DOL_PROFILE_REDIRECT_PROFILE : getParam($CNF['PARAM_REDIRECT_AADD']);
+        $sRedirectDefault = 'page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']];
+
+        $sRedirectUrl = $sRedirectDefault;
+        switch ($sRedirectType) {
+            case BX_DOL_PROFILE_REDIRECT_PROFILE:
+                break;
+
+            // if user just joined the redirect to the page where user comes from. 
+            case BX_DOL_PROFILE_REDIRECT_LAST:
+                $sJoinReferrer = $oSession->getValue('join-referrer');
+                if($sJoinReferrer) {
+                    $sRedirectUrl = $sJoinReferrer;
+                    $oSession->unsetValue('join-referrer');
+                }
+                break;
+
+            case BX_DOL_PROFILE_REDIRECT_CUSTOM:
+                $sRedirectCustom = getParam($CNF['PARAM_REDIRECT_AADD_CUSTOM_URL']);
+                if($sRedirectCustom) {
+                    $sRedirectUrl = $this->prepareCustomRedirectUrl($sRedirectCustom, $aContentInfo);
+                }
+        }
+
+        $sCustomReferrer = $oSession->getValue('custom-referrer');
+        if($sCustomReferrer) {
+            $sRedirectUrl = $sCustomReferrer;
+            $oSession->unsetValue('custom-referrer');
+        }
+
+        if($sRedirectUrl) {
+            if(!bx_has_proto($sRedirectUrl))
+                $sRedirectUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($sRedirectUrl);
+
+            header('Location: ' . $sRedirectUrl);
+            exit;
+        }
+
+        parent::redirectAfterAdd($aContentInfo);
+    }
+}
+
+/** @} */

+ 1580 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/profile/classes/BxBaseModProfileModule.php

@@ -0,0 +1,1580 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_DOL_PROFILE_REDIRECT_PROFILE', 'profile');
+define('BX_DOL_PROFILE_REDIRECT_LAST', 'last');
+define('BX_DOL_PROFILE_REDIRECT_CUSTOM', 'custom');
+
+/**
+ * Base class for profile modules.
+ */
+class BxBaseModProfileModule extends BxBaseModGeneralModule implements iBxDolContentInfoService, iBxDolProfileService
+{
+    protected $_iAccountId;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($CNF['FIELD_ALLOW_POST_TO']))
+            $this->_aSearchableNamesExcept[] = $CNF['FIELD_ALLOW_POST_TO'];
+
+        $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($aInputsAdd = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aResult = parent::serviceGetSearchableFieldsExtended($aInputsAdd);
+        if(!$this->serviceActAsProfile())
+            return $aResult;
+
+        if(!in_array('online', $this->_aSearchableNamesExcept))
+            $aResult['online'] = array(
+                'type' => 'checkbox', 
+                'caption' => $CNF['T']['form_field_online'],
+                'info' => '',
+            	'value' => '1',
+                'values' => '',
+                'pass' => ''
+            );
+
+        if(!empty($CNF['FIELD_PICTURE']) && !in_array($CNF['FIELD_PICTURE'], $this->_aSearchableNamesExcept))
+            $aResult[$CNF['FIELD_PICTURE']] = array(
+                'type' => 'checkbox', 
+                'caption' => $CNF['T']['form_field_picture'],
+                'info' => '',
+            	'value' => '1',
+                'values' => '',
+                'pass' => '',
+                'search_operator' => '>=' 
+            );
+
+        return $aResult;
+    }
+
+	public function servicePrivateProfileMsg()
+    {
+        $mixedContent = $this->_getContent();
+        if ($mixedContent) {
+            list($iContentId, $aContentInfo) = $mixedContent;
+            return $this->checkAllowedView($aContentInfo);
+        }
+        return MsgBox(_t('_sys_access_denied_to_private_content'));
+    }
+    
+	public function serviceGetContentInfoById($iContentId)
+    {
+        return $this->_oDb->getContentInfoById((int)$iContentId);
+    }
+
+	public function serviceGetMenuAddonManageTools()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->unsetPaginate();
+        $iNumTotal = $o->getNum();
+        
+        $o->fillFilters(array(
+			'perofileStatus' => BX_PROFILE_STATUS_PENDING
+        ));
+        $iNum1 = $o->getNum();
+        
+        $iNum2 = 0;
+        $CNF = &$this->_oConfig->CNF;
+        if (isset($CNF['OBJECT_REPORTS'])){
+            $o->fillFilters(array('perofileStatus' => ''));
+            $o->fillFiltersByObjects(array('reported' => array('value' => '0', 'field' => 'reports', 'operator' => '>', 'table' => $CNF['TABLE_ENTRIES'])));
+            $iNum2 = $o->getNum();
+        }
+        return array('counter1_value' => $iNum1, 'counter2_value' => $iNum2, 'counter3_value' => $iNumTotal, 'counter1_caption' => _t('_sys_menu_dashboard_manage_tools_addon_counter1_caption_profile_default'));
+	}
+
+	public function serviceGetMenuAddonManageToolsProfileStats()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'account_id' => getLoggedId(),
+        	'perofileStatus' => ''
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+    public function serviceGetMenuAddonFavoritesProfileStats()
+    {
+    	bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass('favorite', array('user' => bx_get_logged_profile_id()));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+    }
+
+    public function serviceGetSubmenuObject ()
+    {
+        return $this->_oConfig->CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+    }
+
+    public function serviceGetMenuSetNameForMenuTrigger ($sMenuTriggerName)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if (isset($CNF['TRIGGER_MENU_PROFILE_VIEW_SUBMENU']) && $CNF['TRIGGER_MENU_PROFILE_VIEW_SUBMENU'] == $sMenuTriggerName)
+            return $CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+        elseif (isset($CNF['TRIGGER_MENU_PROFILE_SNIPPET_META']) && $CNF['TRIGGER_MENU_PROFILE_SNIPPET_META'] == $sMenuTriggerName)
+            return $CNF['OBJECT_MENU_SNIPPET_META'];
+        else if (isset($CNF['TRIGGER_MENU_PROFILE_VIEW_ACTIONS']) && $CNF['TRIGGER_MENU_PROFILE_VIEW_ACTIONS'] == $sMenuTriggerName) {
+            if(empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']))
+                return $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'];
+            else
+                return array($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'], $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']);
+        }
+
+        return '';
+    }
+
+    public function serviceGetSnippetMenuVars($iProfileId, $bPublic = null)
+    {
+        return $this->_oTemplate->getSnippetMenuVars($iProfileId, $bPublic);
+    }
+        
+	public function serviceGetPageObjectForPageTrigger ($sPageTriggerName)
+    {
+        if (isset($this->_oConfig->CNF['TRIGGER_PAGE_VIEW_ENTRY']) && $this->_oConfig->CNF['TRIGGER_PAGE_VIEW_ENTRY'] == $sPageTriggerName)
+        	return $this->_oConfig->CNF['OBJECT_PAGE_VIEW_ENTRY'];
+
+        return '';
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit)
+    {
+		$aRet = array();
+
+        $a = $this->_oDb->searchByTerm($sTerm, $iLimit);
+        foreach ($a as $r) {
+            $oProfile = BxDolProfile::getInstance($r['profile_id']);
+
+            $aRet[] = array (
+            	'label' => $this->serviceProfileName($r['content_id']), 
+                'value' => $r['profile_id'], 
+                'url' => $oProfile->getUrl(),
+            	'thumb' => $oProfile->getThumb(),
+                'unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+            );
+        }
+
+        return $aRet;
+    }
+
+    public function serviceProfileUnit ($iContentId, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $bCheckPrivateContent = isset($aParams['check_private_content']) ? (bool)$aParams['check_private_content'] : true;
+
+        $sTemplate = 'unit.html';
+        $sTemplateSize = false;
+        $aTemplateVars = array();
+        if(!empty($aParams['template'])) {
+            if(is_string($aParams['template']))
+                $sTemplate = $aParams['template'] . '.html';
+            else if(is_array($aParams['template'])) {
+                if(!empty($aParams['template']['name']))
+                    $sTemplate = $aParams['template']['name'] . '.html';
+
+                if(!empty($aParams['template']['size']))
+                    $sTemplateSize = $aParams['template']['size'];
+
+                if(!empty($aParams['template']['vars']))
+                    $aTemplateVars = $aParams['template']['vars'];
+            }
+        }
+
+        return $this->_oTemplate->unit($aContentInfo, $bCheckPrivateContent, array($sTemplate, $sTemplateSize, $aTemplateVars));
+    }
+
+    public function serviceHasImage ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $mixedContent = $this->_getContent($iContentId, 'getContentInfoById');
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        return !empty($aContentInfo[$CNF['FIELD_PICTURE']]);
+    }
+
+    public function serviceProfilePicture ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlPicture', $iContentId);
+    }
+
+    public function serviceProfileAvatar ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlAvatar', $iContentId);
+    }
+    
+    public function 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;
+        }
+
+        bx_alert('system', 'get_participating_profiles', $iProfileId, false, array(
+            'module' => $this->_oConfig->getName(),
+            'profiles' => &$aRet
+        ));
+
+        return $aRet;
+    }
+
+    /**
+     * Prepare fields from some universal set of fields to fields in particular profile module. 
+     * By default only 'name' and 'description' fields are supported.
+     * After fields convertion it can be used in @see BxBaseModGeneralModule::serviceEntityAdd
+     * @param $aFieldsProfile fields in soem universal format.
+     * @return array which is ready to use for particular module
+     */ 
+    public function servicePrepareFields ($aFieldsProfile)
+    {        
+        bx_alert($this->getName(), 'prepare_fields', 0, 0, array('fields_orig' => $aFieldsProfile, 'fields_result' => &$aFieldsProfile));
+
+        return $aFieldsProfile;
+    }
+
+    protected function _servicePrepareFields ($aFieldsProfile, $aFieldsDefault, $aMap)
+    {
+        $aFieldsOrig = $aFieldsProfile;
+
+        bx_import('BxDolPrivacy');
+        $aFieldsDefault2 = array(
+            'allow_view_to' => BX_DOL_PG_ALL,
+            'allow_post_to' => BX_DOL_PG_FRIENDS,
+        );
+        $aFieldsProfile = array_merge($aFieldsDefault2, $aFieldsDefault, $aFieldsProfile);
+
+        foreach ($aMap as $k => $v) {
+            if (isset($aFieldsProfile[$v]))
+                $aFieldsProfile[$k] = $aFieldsProfile[$v];
+            if ($k != $v && !isset($aMap[$v]))
+                unset($aFieldsProfile[$v]);
+        }
+        
+        bx_alert($this->getName(), 'prepare_fields', 0, 0, array('fields_orig' => $aFieldsOrig, 'fields_result' => &$aFieldsProfile));
+
+        return $aFieldsProfile;
+    }
+
+    public function serviceFormsHelper ()
+    {
+        return parent::serviceFormsHelper ();
+    }
+
+    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 serviceBrowseRelationsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        $oConnection = BxDolConnection::getObjectInstance($sObjectConnections);
+        if (!$oConnection)
+            return '';       
+
+        // set some vars
+        $iStart = (int)bx_get('start');
+        $iLimit = empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? 4 : getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']);
+        if (!$iLimit)
+            $iLimit = 4;
+
+        // get connections array
+        bx_import('BxDolConnection');
+        $aConnections = $oConnection->getConnectionsAsArrayExt($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, $iStart, $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if(empty($aConnections) || !is_array($aConnections))
+            return '';
+
+        $aResult = array();
+        foreach($aConnections as $iProfile => $aConnection)
+            $aResult[] = array(
+                'id' => $iProfile,
+                'info' => array(
+                    'addon' => $oConnection->getRelationTranslation($aConnection['relation'])
+                )
+            );
+
+        return $this->_serviceBrowseQuick($aResult, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseConnectionsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        $oConnection = BxDolConnection::getObjectInstance($sObjectConnections);
+        if (!$oConnection)
+            return '';
+
+        // set some vars
+        $iLimit = empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? 4 : getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']);
+        if (!$iLimit)
+            $iLimit = 4;
+        $iStart = (int)bx_get('start');
+
+        // get connections array
+        bx_import('BxDolConnection');
+        $a = $oConnection->getConnectionsAsArray ($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, (int)bx_get('start'), $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if (!$a)
+            return '';
+
+        return $this->_serviceBrowseQuick($a, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseConnectionsByType ($aParamsCnn, $aParamsBrs = array())
+    {
+        $sAll = 'all';
+
+        $aParamsCnn['object'] = !empty($aParamsCnn['object']) ? $aParamsCnn['object'] : 'sys_profiles_friends';
+        $aParamsCnn['type'] = !empty($aParamsCnn['type']) ? $aParamsCnn['type'] : 'content';
+        $aParamsCnn['profile_id2'] = !empty($aParamsCnn['profile_id2']) ? (int)$aParamsCnn['profile_id2'] : 0;
+        $aParamsCnn['mutual'] = isset($aParamsCnn['mutual']) ? $aParamsCnn['mutual'] : false;
+
+        $oConnection = BxDolConnection::getObjectInstance($aParamsCnn['object']);
+        if(!$oConnection)
+            return '';
+
+        $aSQLParts = $oConnection->getConnectionsAsSQLParts ($aParamsCnn['type'], 'sys_profiles', 'id', $aParamsCnn['profile_id'], $aParamsCnn['profile_id2'], $aParamsCnn['mutual']);
+
+        $sType = !empty($aParamsBrs['type']) ? $aParamsBrs['type'] : $sAll;
+        if(bx_get('type') !== false)
+            $sType = bx_process_input(bx_get('type'));
+
+        $iStart = (int)bx_get('start');
+
+        $iLimitDefault = 4;
+        $iLimit = !empty($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? getParam($this->_oConfig->CNF['PARAM_NUM_CONNECTIONS_QUICK']) : $iLimitDefault;
+        if(!empty($aParamsBrs['per_page']))
+            $iLimit = (int)$aParamsBrs['per_page'];
+        if(!$iLimit)
+            $iLimit = $iLimitDefault;
+
+        $aProfiles = BxDolProfileQuery::getInstance()->getConnectedProfilesByType($aSQLParts, ($sType != $sAll ? $sType : ''), $iStart, $iLimit + 1);
+
+        $mixedMenu = '';
+        if(!isset($aParamsBrs['filter_menu']) || $aParamsBrs['filter_menu'] === true) {
+            $aModules = array(
+                array('name' => $sAll)
+            );
+            $aModules = array_merge($aModules, BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1)));
+
+            $aMenuItems = array();
+            foreach($aModules as $aModule)
+                if($aModule['name'] == $sAll || BxDolRequest::serviceExists($aModule['name'], 'act_as_profile'))
+                    $aMenuItems[] = array(
+                    	'id' => $aModule['name'], 
+                    	'name' => $aModule['name'], 
+                    	'class' => '', 
+                    	'link' => 'javascript:void(0)', 
+                    	'onclick' => "return !loadDynamicBlockAutoPaginate(this, " . $iStart . ", " . $iLimit . ", " . bx_js_string(json_encode(array('type' => $aModule['name']))) . ");", 
+                    	'target' => '_self', 
+                    	'title' => _t('_' . $aModule['name']), 
+                    	'active' => 1
+                    );
+    
+            if(!empty($aMenuItems)) {
+                $mixedMenu = new BxTemplMenu(array('template' => 'menu_vertical.html', 'menu_id'=> $this->_oConfig->getName() . '-connections-by-type', 'menu_items' => $aMenuItems));
+                $mixedMenu->setSelected('', $sType);
+            }
+        }
+
+        return array(
+            'menu' => $mixedMenu,
+            'content' => $this->_serviceBrowseQuick(array_keys($aProfiles), $iStart, $iLimit, array('type' => $sType))
+        );
+    }
+
+	public function serviceBrowseByAcl ($mixedLevelId, $iDesignBox = BX_DB_PADDING_DEF)
+    {
+        return $this->_serviceBrowse (
+            'acl',
+            array(
+                'level' => $mixedLevelId,
+			),
+            $iDesignBox,
+            true
+        );
+    }
+
+    public function serviceEntityEditCover ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT_COVER']);
+    }
+
+	/**
+     * Entry comments
+     */
+    public function serviceEntityCommentsByProfile ($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_COMMENTS']))
+            return '';
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        return $this->_entityComments($CNF['OBJECT_COMMENTS'], $oProfile->getContentId());
+    }
+
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;    
+
+        return parent::serviceEntitySocialSharing(array($iContentId, $aContentInfo), array(
+            'id_thumb' => !empty($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]) ? $aContentInfo[$CNF['FIELD_PICTURE']] : 0, 
+        ));
+    }
+
+    public function serviceProfileMembership ($iContentId = 0)
+    {
+    	$mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+		return BxDolAcl::getInstance()->getProfileMembership($aContentInfo['profile_id']);
+    }
+
+    public function serviceProfileFriends ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseConnectionsQuick ($aContentInfo['profile_id'], 'sys_profiles_friends', BX_CONNECTIONS_CONTENT_TYPE_CONTENT, true);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+        return $s;
+    }
+
+    public function serviceProfileSubscriptions ($iContentId = 0, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $aResult = $this->serviceBrowseConnectionsByType(array(
+        	'profile_id' => $aContentInfo['profile_id'],  
+        	'object' => 'sys_profiles_subscriptions', 
+            'type' => BX_CONNECTIONS_CONTENT_TYPE_CONTENT
+        ), array(
+            'type' => isset($aParams['type']) ? $aParams['type'] : '',
+            'filter_menu' => isset($aParams['filter_menu']) ? $aParams['filter_menu'] : true,
+        ));
+        if(empty($aResult['content']))
+            $aResult['content'] = MsgBox(_t('_sys_txt_empty'));
+
+        return $aResult;
+    }
+
+    public function serviceProfileSubscribedMe ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseConnectionsQuick ($aContentInfo['profile_id'], 'sys_profiles_subscriptions', BX_CONNECTIONS_CONTENT_TYPE_INITIATORS);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    public function serviceProfileRelations ($iContentId = 0, $aParams = array())
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseRelationsQuick ($aContentInfo['profile_id'], 'sys_profiles_relations', BX_CONNECTIONS_CONTENT_TYPE_CONTENT, 1);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    public function serviceProfileRelatedMe ($iContentId = 0)
+    {
+        $mixedContent = $this->_getContent($iContentId);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        bx_import('BxDolConnection');
+        $s = $this->serviceBrowseRelationsQuick ($aContentInfo['profile_id'], 'sys_profiles_relations', BX_CONNECTIONS_CONTENT_TYPE_INITIATORS, 1);
+        if (!$s)
+            return MsgBox(_t('_sys_txt_empty'));
+
+        return $s;
+    }
+
+    /**
+     * check enabled profile activation letter
+     */
+    public function  serviceIsEnableProfileActivationLetter()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        return isset($CNF['PARAM_ENABLE_ACTIVATION_LETTER']) ? (bool)getParam($CNF['PARAM_ENABLE_ACTIVATION_LETTER']) : true;
+    }
+
+    public function serviceIsEnableRelations()
+    {
+        $sModule = $this->_oConfig->getName();
+        $oRelations = BxDolConnection::getObjectInstance('sys_profiles_relations');
+        return $oRelations->isRelationAvailableWithProfile($sModule) || $oRelations->isRelationAvailableFromProfile($sModule);
+    }
+
+    /**
+     * For internal usage only.
+     */
+    public function serviceDeleteEntityService ($iContentId, $bDeleteWithContent = false)
+    {
+        return parent::serviceDeleteEntity ($iContentId, 'deleteDataService');
+    }
+
+	/**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+        $a = parent::serviceGetNotificationsData();
+
+        $sModule = $this->_aModule['name'];
+        
+        $a['handlers'][] = array('group' => $sModule . '_timeline_post_common', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'timeline_post_common', 'module_name' => $sModule, 'module_method' => 'get_notifications_timeline_post_common', 'module_class' => 'Module');
+
+        $a['settings'][] = array('group' => 'timeline_post', 'unit' => $sModule, 'action' => 'timeline_post_common', 'types' => array('follow_member'));
+
+        $a['alerts'][] = array('unit' => $sModule, 'action' => 'timeline_post_common');
+
+        return $a;
+    }
+
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $aResult = parent::serviceGetNotificationsVote($aEvent);
+        if(empty($aResult) || !is_array($aResult) || !$this->serviceActAsProfile())
+            return $aResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType((int)$aEvent['object_id'], $this->_oConfig->getName());
+        if($oProfile !== false)
+            $aResult['entry_author'] = $oProfile->id();
+
+        return $aResult;
+    }
+
+    public function serviceGetNotificationsVote($aEvent)
+    {
+        $aResult = parent::serviceGetNotificationsVote($aEvent);
+        if(empty($aResult) || !is_array($aResult) || !$this->serviceActAsProfile())
+            return $aResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType((int)$aEvent['object_id'], $this->_oConfig->getName());
+        if($oProfile !== false)
+            $aResult['entry_author'] = $oProfile->id();
+
+        return $aResult;
+    }
+
+    /**
+     * Notification about new member requst in the group
+     */
+    public function serviceGetNotificationsTimelinePostCommon($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return array();
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+            
+        $aSubcontentInfo = BxDolService::call('bx_timeline', 'get_info', array((int)$aEvent['subobject_id'], false));
+        if(empty($aSubcontentInfo) || !is_array($aSubcontentInfo))
+            return array();
+
+        $sSubentryUrl = BxDolService::call('bx_timeline', 'get_link', array((int)$aEvent['subobject_id']));
+        $sSubentrySample = $aSubcontentInfo['title'];
+        if(empty($sSubentrySample))
+            $sSubentrySample = strmaxtextlen($aSubcontentInfo['description'], 20, '...');
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $oGroupProfile->getUrl(),
+            'entry_caption' => $oGroupProfile->getDisplayName(),
+            'entry_author' => $oGroupProfile->id(),
+            'subentry_sample' => $sSubentrySample,
+            'subentry_url' => $sSubentryUrl,
+            'lang_key' => $CNF['T']['txt_ntfs_timeline_post_common'],
+        );
+    }
+
+	/**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sModule = $this->_aModule['name'];
+
+        $aAlerts = array();
+        $aHandlers = array();
+        if(!empty($CNF['FIELD_PICTURE'])) {
+            $aAlerts = array_merge($aAlerts, array(
+                array('unit' => $sModule, 'action' => 'profile_picture_changed'),
+                array('unit' => $sModule, 'action' => 'profile_picture_deleted')
+            ));
+            $aHandlers = array_merge($aHandlers, array(
+                array('group' => $sModule . '_profile_picture', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'profile_picture_changed', 'module_name' => $sModule, 'module_method' => 'get_timeline_profile_picture', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_profile_picture', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'profile_picture_deleted')
+            ));
+        }
+
+        if(!empty($CNF['FIELD_COVER'])) {
+            $aAlerts = array_merge($aAlerts, array(
+                array('unit' => $sModule, 'action' => 'profile_cover_changed'),
+                array('unit' => $sModule, 'action' => 'profile_cover_deleted')
+            ));
+            $aHandlers = array_merge($aHandlers, array(
+                array('group' => $sModule . '_profile_cover', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'profile_cover_changed', 'module_name' => $sModule, 'module_method' => 'get_timeline_profile_cover', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_profile_cover', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'profile_cover_deleted')
+            ));
+        }
+
+    	$aResult = array();
+        if(!empty($aAlerts) && !empty($aHandlers))
+            $aResult = array(
+            	'handlers' => $aHandlers,
+            	'alerts' => $aAlerts
+            );
+
+        return $aResult;
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelineProfilePicture($aEvent, $aBrowseParams = array())
+    {
+        $aResult = $this->_serviceGetTimelineProfileImage($aEvent, $aBrowseParams, array(
+            'stg' => 'OBJECT_STORAGE',
+            'trans' => array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_AVATAR'),
+            'trans_orig' => array('OBJECT_IMAGES_TRANSCODER_PICTURE', 'OBJECT_IMAGES_TRANSCODER_GALLERY'),
+            'txt_ss' => 'txt_sample_pp_single',
+            'txt_sswa' => 'txt_sample_pp_single_with_article',
+            'txt_sa' => 'txt_sample_pi_action',
+            'txt_sau' => 'txt_sample_pi_action_user'
+        ));
+        $aResult['allowed_view'] = array('module' => $this->_oConfig->getName(), 'method' => 'get_timeline_profile_picture_allowed_view');
+
+        return $aResult;
+    }
+
+    public function serviceGetTimelineProfilePictureAllowedView($aEvent)
+    {
+        return $this->_serviceGetTimelineProfileImageAllowedView($aEvent);
+    }
+
+    public function serviceGetTimelineProfileCover($aEvent, $aBrowseParams = array())
+    {
+        $aResult = $this->_serviceGetTimelineProfileImage($aEvent, $aBrowseParams, array(
+            'stg' => 'OBJECT_STORAGE_COVER',
+            'trans' => array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_COVER_THUMB'),
+            'trans_orig' => array('OBJECT_IMAGES_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_GALLERY'),
+            'txt_ss' => 'txt_sample_pc_single',
+            'txt_sswa' => 'txt_sample_pc_single_with_article',
+            'txt_sa' => 'txt_sample_pi_action',
+            'txt_sau' => 'txt_sample_pi_action_user'
+        ));
+        $aResult['allowed_view'] = array('module' => $this->_oConfig->getName(), 'method' => 'get_timeline_profile_cover_allowed_view');
+
+        return $aResult;
+    }
+    
+    public function serviceGetTimelineProfileCoverAllowedView($aEvent)
+    {
+        return $this->_serviceGetTimelineProfileImageAllowedView($aEvent);
+    }
+
+    public function serviceGetConnectionButtonsTitles($iProfileId, $sConnectionsObject = 'sys_profiles_friends')
+    {
+        if (!isLogged())
+            return array();
+
+        if (!($oConn = BxDolConnection::getObjectInstance($sConnectionsObject)))
+            return array();
+
+        $CNF = $this->_oConfig->CNF;
+
+        if ($oConn->isConnectedNotMutual(bx_get_logged_profile_id(), $iProfileId)) {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend_sent']),
+                'remove' => _t($CNF['T']['menu_item_title_unfriend_cancel_request']),
+            );
+        } elseif ($oConn->isConnectedNotMutual($iProfileId, bx_get_logged_profile_id())) {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend_confirm']),
+                'remove' => _t($CNF['T']['menu_item_title_unfriend_reject_request']),
+            );
+        } elseif ($oConn->isConnected($iProfileId, bx_get_logged_profile_id(), true)) {
+            return array(
+                'add' => '',
+                'remove' => _t($CNF['T']['menu_item_title_unfriend']),
+            );
+        } else {
+            return array(
+                'add' => _t($CNF['T']['menu_item_title_befriend']),
+                'remove' => '',
+            );
+        }
+    }
+
+
+    // ====== PERMISSION METHODS
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedProfileView
+     */ 
+    public function serviceCheckAllowedProfileView($iContentId)
+    {        
+        return $this->serviceCheckAllowedWithContent('View', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckAllowedPostInProfile
+     */
+    public function serviceCheckAllowedPostInProfile($iContentId)
+    {
+        return $this->serviceCheckAllowedWithContent('Post', $iContentId);
+    }
+
+    /**
+     * @see iBxDolProfileService::serviceCheckSpacePrivacy
+     */ 
+    public function serviceCheckSpacePrivacy($iContentId)
+    {
+        return $this->serviceCheckAllowedProfileView($iContentId);
+    }
+
+    /**
+     * Check if the profile can be viewed.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedProfileView service method or 
+     * BxDolProfile::checkAllowedProfileView method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($oProfile && $oProfile->id() == $iProfileId)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return parent::serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * Check if posting (comment, post in Timeline) is available.
+     * 
+     * NOTE. This service should be used if it's needed to pass some specific values in 
+     * $isPerformAction and $iProfileId parameters, otherwise it's recommended to use 
+     * BxBaseModProfileModule::serviceCheckAllowedPostInProfile service method or 
+     * BxDolProfile::checkAllowedPostInProfile method.
+     * 
+     * @param type $aDataEntry - entry which the action will be performed for
+     * @param type $isPerformAction - perform or just check the action
+     * @param type $iProfileId - performer's profile ID
+     * @return integer - one of CHECK_ACTION_RESULT_XXX constants.
+     */
+    public function serviceCheckAllowedPostForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        // check is view allowed
+        if(($mixedResult = $this->serviceCheckAllowedViewForProfile($aDataEntry, $isPerformAction, $iProfileId)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        // moderator and owner always have access
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if(($oProfile && $oProfile->id() == $iProfileId) || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check privacy
+        if(!empty($CNF['OBJECT_PRIVACY_POST'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST']);
+            if($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_post', 0, 0, array('module' => $this->getName(), 'content_info' => $aDataEntry, 'profile_id' => $iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function serviceSetViewProfileCover($oPage, $aProfileInfo)
+    {
+        $this->_oTemplate->setCover($oPage,$aProfileInfo);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedAdd ($isPerformAction = false)
+    {
+        $oAccount = BxDolAccount::getInstance();
+        if (!$oAccount || ($this->serviceActAsProfile() && $oAccount->isProfilesLimitReached()))
+            return _t('_sys_txt_access_denied');
+        return parent::checkAllowedAdd ($isPerformAction);
+    }   
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewProfileImage ($aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // check privacy
+        if (empty($CNF['OBJECT_PRIVACY_VIEW'])) 
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']]) && !$oPrivacy->isPartiallyVisible($aDataEntry[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            return _t('_sys_access_denied_to_private_content');
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedViewCoverImage ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedViewProfileImage($aDataEntry);
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedPost ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedPostForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function 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 checkAllowedRelationAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aDataEntry)))
+            return $sMsg;
+
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_relations', false, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedRelationRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_relations', false, true);
+    }
+
+    public function checkAllowedRelationsView (&$aDataEntry, $isPerformAction = false)
+    {
+        $sResult = _t('_sys_txt_access_denied');
+
+        $sModule = $this->_oConfig->getName();
+        $oRelations = BxDolConnection::getObjectInstance('sys_profiles_relations');
+        if(!$oRelations->isRelationAvailableWithProfile($sModule) && !$oRelations->isRelationAvailableFromProfile($sModule))
+            return $sResult;
+
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return $sResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedView($aDataEntry)))
+            return $sMsg;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_subscriptions', false, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_subscriptions', false, true);
+    }
+
+    public function checkAllowedSubscriptionsView (&$aDataEntry, $isPerformAction = false)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sResult = _t('_sys_txt_access_denied');
+        if(empty($aDataEntry) || !is_array($aDataEntry))
+            return $sResult;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name']);
+        if(!$oProfile || ($oProfile->id() != $this->_iProfileId && $this->_oDb->getParam($CNF['PARAM_PUBLIC_SBSN']) != 'on' && $this->_oDb->getParam($CNF['PARAM_PUBLIC_SBSD']) != 'on'))
+            return $sResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkMyself($iContentId)
+    {
+		$iLogged = (int)bx_get_logged_profile_id();
+    	if(empty($iLogged))
+    		return false;
+
+    	$oProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->_oConfig->getName());
+    	if(!$oProfile)
+    		return false;
+
+		return $oProfile->id() == $iLogged;
+    }
+
+    // ====== COMMON METHODS
+    public function alertAfterAdd($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $aParams = array();        
+        if(isset($aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']]))
+            $aParams['privacy_view'] = $aContentInfo[$CNF['FIELD_ALLOW_VIEW_TO']];
+
+        bx_alert($sModule, 'added', $iContentId, false, $aParams);
+    }
+
+    public function alertAfterEdit($aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sModule = $this->getName();
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $sModule);
+
+        bx_alert($sModule, 'edited', $iContentId);
+        bx_alert('profile', 'edit', $oProfile->id(), 0, array('content' => $iContentId, 'module' => $sModule));
+    }
+
+    public function getProfileByCurrentUrl ()
+    {
+        $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        
+        if ($iProfileId)
+            return  BxDolProfile::getInstance($iProfileId);
+
+        $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if ($iContentId)
+            return BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        
+        return false;
+    }
+
+    // ====== PROTECTED METHODS
+
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        if (!$this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name']);
+        if (!$oProfile || $oProfile->id() == $this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        return BxDolConnection::getObjectInstance($sObjConnection)->checkAllowedConnect ($this->_iProfileId, $oProfile->id(), $isPerformAction, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    protected function _buildRssParams($sMode, $aArgs)
+    {
+        $aParams = array ();
+        $sMode = bx_process_input($sMode);
+        switch ($sMode) {
+            case 'connections':
+                $aParams = array(
+                    'object' => isset($aArgs[0]) ? $aArgs[0] : '',
+                    'type' => isset($aArgs[1]) ? $aArgs[1] : '',
+                    'profile' => isset($aArgs[2]) ? (int)$aArgs[2] : 0,
+                    'mutual' => isset($aArgs[3]) ? (int)$aArgs[3] : 0,
+                    'profile2' => isset($aArgs[4]) ? (int)$aArgs[4] : 0,
+                );
+                break;
+        }
+
+        return $aParams;
+    }
+
+    protected function _serviceBrowseQuick($aProfiles, $iStart = 0, $iLimit = 4, $aAdditionalParams = array())
+    {
+        // get paginate object
+        $oPaginate = new BxTemplPaginate(array(
+            'on_change_page' => "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}', " . bx_js_string(json_encode($aAdditionalParams)) . ");",
+            'num' => count($aProfiles),
+            'per_page' => $iLimit,
+            'start' => $iStart,
+        ));
+
+        // remove last item from connection array, because we've got one more item for pagination calculations only
+        if (count($aProfiles) > $iLimit)
+            array_pop($aProfiles);
+
+        // get profiles HTML
+        $s = '';
+        foreach ($aProfiles as $mixedProfile) {
+            $bProfile = is_array($mixedProfile);
+
+            $oProfile = BxDolProfile::getInstance($bProfile ? (int)$mixedProfile['id'] : (int)$mixedProfile);
+            if(!$oProfile)
+                continue;
+
+            $aUnitParams = array();
+            if($bProfile && is_array($mixedProfile['info']))
+                $aUnitParams = array('template' => array('vars' => $mixedProfile['info']));
+
+            $s .= $oProfile->getUnit(0, $aUnitParams);
+        }
+
+        // return profiles + paginate
+        return $s . (!$iStart && $oPaginate->getNum() <= $iLimit ?  '' : $oPaginate->getSimplePaginate());
+    }
+
+    protected function _serviceGetTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aFileInfo = BxDolStorage::getObjectInstance($CNF[$aBuildParams['stg']])->getFile((int)$aEvent['object_id']);
+        if(empty($aFileInfo) || !is_array($aFileInfo))
+            return false;
+            
+        $aEventContent = unserialize($aEvent['content']);
+        if(!is_array($aEventContent) || empty($aEventContent['content']))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEventContent['content']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo['profile_id']);
+        if (!$oProfile->isActive())
+            return false;
+            
+        $sUserName = $oProfile->getDisplayName();
+
+        $sSample = isset($CNF['T'][$aBuildParams['txt_sswa']]) ? $CNF['T'][$aBuildParams['txt_sswa']] : $CNF['T'][$aBuildParams['txt_ss']];
+
+        //--- Title & Description
+        $sTitle = !empty($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TEXT']];
+
+        $sDescription = _t($CNF['T'][$aBuildParams['txt_sau']], $sUserName, _t($sSample));
+
+        return array(
+            'owner_id' => $aEvent['owner_id'],
+            'object_owner_id' => $aContentInfo['profile_id'],
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => $sSample,
+            'sample_wo_article' => $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_action' => isset($CNF['T'][$aBuildParams['txt_sa']]) ? $CNF['T'][$aBuildParams['txt_sa']] : '',
+            'url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'content' => $this->_getContentForTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams, $aContentInfo, $aFileInfo), //a string to display or array to parse default template before displaying.
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => '',
+            'votes' => '',
+            'scores' => '',
+            'reports' => '',
+            'comments' => '',
+            'title' => $sTitle, //may be empty.
+            'description' => $sDescription //may be empty.
+        );
+    }
+
+    protected function _serviceGetTimelineProfileImageAllowedView($aEvent)
+    {
+        $sError = _t('_sys_access_denied_to_private_content');
+
+        if(empty($aEvent['content']) || !is_array($aEvent['content']) || empty($aEvent['content']['id']))
+            return $sError;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['content']['id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return $sError;
+
+        return $this->serviceCheckAllowedViewForProfile($aContentInfo);
+    }
+
+    protected function _getContentForTimelineProfileImage($aEvent, $aBrowseParams, $aBuildParams, $aContentInfo, $aFileInfo)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+    	//--- Image(s)
+        $sImage = $this->_oConfig->getImageUrl($aFileInfo['id'], $aBuildParams['trans']);
+        $sImageOrig = $this->_oConfig->getImageUrl($aFileInfo['id'], $aBuildParams['trans_orig']);
+        if(!empty($sImage)) {
+            if(empty($sImageOrig))
+                $sImageOrig = $sImage;
+
+            $a = array('url' => $sUrl, 'src' => $sImage);
+            if (CHECK_ACTION_RESULT_ALLOWED === $this->checkAllowedView($aContentInfo))
+                $a['src_orig'] = $sImageOrig;
+            $aImages = array($a);
+        }
+
+    	return array(
+            'sample' => isset($CNF['T'][$aBuildParams['txt_sswa']]) ? $CNF['T'][$aBuildParams['txt_sswa']] : $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_wo_article' => $CNF['T'][$aBuildParams['txt_ss']],
+            'sample_action' => isset($CNF['T'][$aBuildParams['txt_sa']]) ? $CNF['T'][$aBuildParams['txt_sa']] : '',
+            'id' => $aContentInfo[$CNF['FIELD_ID']],
+            'url' => $sUrl,
+            'title' =>  '',
+            'text' => '',
+            'images' => $aImages,
+            'videos' => array()
+        );
+    }
+
+    protected function _entityComments($sObject, $iId = 0)
+    {
+        if(!$iId)
+            $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iId)
+            $iId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        if(!$iId)
+            return false;
+
+        $oCmts = BxDolCmts::getObjectInstance($sObject, $iId);
+        if(!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getCommentsBlock(array(), array('in_designbox' => false));
+    }
+
+    protected function _getContent($iContentId = 0, $sFuncGetContent = 'getContentInfoById')
+    {
+        if(!$iContentId && bx_get('id') === false && bx_get('profile_id') !== false) {
+            $oProfile = BxDolProfile::getInstance((int)bx_get('profile_id'));
+            if($oProfile)
+                $iContentId = $oProfile->getContentId();
+        }
+
+        return parent::_getContent($iContentId, $sFuncGetContent);
+    }
+
+    /** Returns list of members by mode with limited number of records for React Jot
+     * @param string $sMode
+     * @param int $iStart
+     * @param int $iPerPage
+     * @return mixed
+     */
+
+    public function serviceGetMembers($sMode = 'active', $iStart = 0, $iPerPage = 10){
+        bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass($sMode);
+
+        $o -> aCurrent['paginate'] = array('perPage' => $iPerPage, 'forceStart' => $iStart);
+        return $o -> getSearchData();
+    }
+}
+
+/** @} */

+ 118 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/classes/BxBaseModTextFormsEntryHelper.php

@@ -0,0 +1,118 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxBaseModTextFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
+{
+    public function __construct($oModule)
+    {
+        parent::__construct($oModule);
+    }
+
+    /**
+     * @return main content text
+     */
+    public function viewDataText ($iContentId)
+    {
+        $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->checkAllowedView($aContentInfo)))
+            return MsgBox($sMsg);
+
+        return $aContentInfo[$CNF['FIELD_TEXT']];
+    }
+
+    /**
+     * @return array of profile object and content info
+     */
+    protected function _getProfileAndContentData ($iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+        if(!$aContentInfo)
+            return array(false, false);
+
+        $oProfile = BxDolProfile::getInstanceMagic($aContentInfo[$CNF['FIELD_AUTHOR']]);
+        return array($oProfile, $aContentInfo);
+    }
+
+    public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
+    {
+        if ($s = parent::onDataEditAfter($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm))
+            return $s;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!($aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId)))
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        if (isset($CNF['FIELD_VIDEO']))
+            $oForm->processFiles($CNF['FIELD_VIDEO'], $iContentId, false);
+
+        if (isset($CNF['FIELD_FILE']))
+            $oForm->processFiles($CNF['FIELD_FILE'], $iContentId, false);
+
+        if (isset($CNF['FIELD_POLL']))
+            $oForm->processPolls($CNF['FIELD_POLL'], $iContentId);
+
+        // change profile to 'pending' only if profile is 'active'
+        if ($oProfile->isActive() && !empty($aTrackTextFieldsChanges['changed_fields']))
+            $oProfile->disapprove(BX_PROFILE_ACTION_AUTO);
+
+        return '';
+    }
+
+    public function onDataAddAfter ($iAccountId, $iContentId)
+    {
+        if ($s = parent::onDataAddAfter($iAccountId, $iContentId))
+            return $s;
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!($aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId)))
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        if(($oForm = $this->getObjectFormAdd()) !== false) {
+            if (isset($CNF['FIELD_VIDEO']))
+                $oForm->processFiles($CNF['FIELD_VIDEO'], $iContentId, true);
+
+            if (isset($CNF['FIELD_FILE']))
+                $oForm->processFiles($CNF['FIELD_FILE'], $iContentId, true);
+
+            if (isset($CNF['FIELD_POLL']))
+                $oForm->processPolls($CNF['FIELD_POLL'], $iContentId);
+        }
+
+        return '';
+    }
+
+    public function onDataDeleteAfter($iContentId, $aContentInfo, $oProfile)
+    {
+        $sResult = parent::onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile);
+        if(!empty($sResult))
+            return $sResult;
+
+        $this->_oModule->_oDb->deletePolls(array('content_id' => $iContentId));
+
+        return '';
+    }
+}
+
+/** @} */

+ 778 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/classes/BxBaseModTextTemplate.php

@@ -0,0 +1,778 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Module representation.
+ */
+class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
+{
+    protected $_sUnitClassShowCase;
+    
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+        
+        $this->_sUnitClassShowCase = 'bx-base-unit-showcase bx-base-text-unit-showcase bx-def-margin-sec-bottom';
+    }
+
+    public function getJsCode($sType, $aParams = array(), $bWrap = true)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aParams = array_merge(array(
+            'aHtmlIds' => $this->_oConfig->getHtmlIds(),
+            'sEditorId' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : ''
+        ), $aParams);
+
+        return parent::getJsCode($sType, $aParams, $bWrap);
+    }
+
+    function unit ($aData, $isCheckPrivateContent = true, $sTemplateName = 'unit.html', $aParams = array())
+    {
+    	$sResult = $this->checkPrivacy ($aData, $isCheckPrivateContent, $this->getModule(), $sTemplateName);
+    	if($sResult)
+            return $sResult;
+        $aParams['template_name'] = $sTemplateName;
+		return $this->parseHtmlByName($sTemplateName, $this->getUnit($aData, $aParams));
+    }
+
+    function entryAttachments ($aData, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aStoragesKeys = array('OBJECT_STORAGE_PHOTOS', 'OBJECT_STORAGE_VIDEOS', 'OBJECT_STORAGE_FILES');
+
+        $aStorages = array();
+        foreach($aStoragesKeys as $sKey)
+            if(!empty($CNF[$sKey]))
+                $aStorages[] = $CNF[$sKey];
+
+        if(!empty($aStorages))
+            return $this->entryAttachmentsByStorage($aStorages, $aData, array_merge($aParams, array('filter_field' => '')));
+
+        return parent::entryAttachments($aData, $aParams);
+    }
+
+    function entryAuthor ($aData, $iProfileId = false, $sFuncAuthorDesc = 'getAuthorDesc', $sTemplateName = 'author.html', $sFuncAuthorAddon = 'getAuthorAddon')
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        if (!$iProfileId)
+            $iProfileId = $aData[$CNF['FIELD_AUTHOR']];
+        
+        $oProfile = BxDolProfile::getInstanceMagic($iProfileId);
+        $sName = $oProfile->getDisplayName();
+        $sAddon = $sFuncAuthorAddon && is_a($oProfile, 'BxDolProfile') ? $this->$sFuncAuthorAddon($aData, $oProfile) : '';        
+
+        $aVars = array (
+            'author_url' => $oProfile->getUrl(),
+            'author_thumb_url' => $oProfile->getThumb(),
+            'author_unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info')),
+            'author_title' => $sName,
+            'author_title_attr' => bx_html_attribute($sName),
+            'author_desc' => $sFuncAuthorDesc ? $this->$sFuncAuthorDesc($aData, $oProfile) : '',
+            'author_profile_desc' => $this->getAuthorProfileDesc($aData, $oProfile),
+            'bx_if:addon' => array (
+                'condition' => (bool)$sAddon,
+                'content' => array (
+                    'content' => $sAddon,
+                ),
+            ),
+        );
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    public function entryBreadcrumb($aContentInfo, $aTmplVarsItems = array())
+    {
+        if(!empty($aTmplVarsItems) && is_array($aTmplVarsItems))
+            return parent::entryBreadcrumb($aContentInfo, $aTmplVarsItems);
+
+    	$CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aTmplVarsItems = array();
+        if(!empty($CNF['OBJECT_CATEGORY']) && !empty($CNF['FIELD_CATEGORY'])) {
+            $oCategory = BxDolCategory::getObjectInstance($CNF['OBJECT_CATEGORY']);
+
+            $aTmplVarsItems[] = array(
+                'url' => $oCategory->getCategoryUrl($aContentInfo[$CNF['FIELD_CATEGORY']]),
+                'title' => $oCategory->getCategoryTitle($aContentInfo[$CNF['FIELD_CATEGORY']])
+            );
+        }
+
+    	$aTmplVarsItems[] = array(
+            'url' => BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'title' => bx_process_output($aContentInfo[$CNF['FIELD_TITLE']])
+        );
+
+    	return parent::entryBreadcrumb($aContentInfo, $aTmplVarsItems);
+    }
+
+    function entryContext ($aData, $iProfileId = false, $sFuncContextDesc = 'getContextDesc', $sTemplateName = 'context.html', $sFuncContextAddon = 'getContextAddon')
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $iContextId = $aData[$CNF['FIELD_ALLOW_VIEW_TO']];
+        if ($iContextId >= 0)
+            return '';
+        
+        $iProfileId = - $iContextId;
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if (!$oProfile) 
+            $oProfile = BxDolProfileUndefined::getInstance();
+
+        if (!$oProfile)
+            return '';
+
+        $sName = $oProfile->getDisplayName();
+        $sAddon = $sFuncContextAddon ? $this->$sFuncContextAddon($aData, $oProfile) : '';
+
+        $aVars = array (
+            'author_url' => $oProfile->getUrl(),
+            'author_thumb_url' => $oProfile->getThumb(),
+            'author_unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info')),
+            'author_title' => $sName,
+            'author_title_attr' => bx_html_attribute($sName),
+            'author_desc' => $sFuncContextDesc ? $this->$sFuncContextDesc($aData) : '',
+            'bx_if:addon' => array (
+                'condition' => (bool)$sAddon,
+                'content' => array (
+                    'content' => $sAddon,
+                ),
+            ),
+        );
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    public function entryPolls($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        
+        $aPolls = $this->_oDb->getPolls(array(
+            'type' => 'content_id', 
+            'content_id' => $aData[$CNF['FIELD_ID']], 
+        ));
+        if(empty($aPolls) || !is_array($aPolls))
+            return;
+
+        $iProfileId = bx_get_logged_profile_id();
+
+        $sPolls = '';
+        foreach($aPolls as $aPoll)
+            $sPolls .= $this->getPollItem($aPoll, $iProfileId);
+
+        if(empty($sPolls))
+            return '';
+
+        $this->addCss(array('polls.css'));
+        $this->addJs(array('polls.js'));
+        return $this->getJsCode('poll') . $this->parseHtmlByName('poll_items.html', array(
+            'polls' => $sPolls
+        ));
+    }
+
+    public function entryPollAnswers($aPoll, $bDynamic = false)
+    {
+        $sContent = $this->_getPollAnswers($aPoll, $bDynamic);
+        if(empty($sContent))
+            return '';
+
+    	return array(
+            'content' => $sContent,
+            'menu' => $this->_getPollBlockMenu($aPoll, 'answers')
+        ); 
+    }
+
+    public function entryPollResults($aPoll, $bDynamic = false)
+    {
+        $sContent = $this->_getPollResults($aPoll, $bDynamic);
+        if(empty($sContent))
+            return '';
+
+        return array(
+            'content' => $sContent,
+            'menu' => $this->_getPollBlockMenu($aPoll, 'results')
+        );  
+    }
+
+    public function getPollForm()
+    {
+        $aForm = $this->getModule()->getPollForm();
+
+        return $this->parseHtmlByName('poll_form.html', array(
+            'js_object' => $this->_oConfig->getJsObject('poll'),
+            'form_id' => $aForm['form_id'],
+            'form' => $aForm['form'],
+        ));
+    }
+    
+    public function getPollField($iContentId = 0, $iProfileId = 0)
+    {
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id();
+
+        $aPolls = array();
+        if(!empty($iContentId))
+            $aPolls = array_merge($aPolls, $this->_oDb->getPolls(array(
+                'type' => 'content_id', 
+                'content_id' => $iContentId, 
+            )));
+
+        $aPolls = array_merge($aPolls, $this->_oDb->getPolls(array(
+            'type' => 'author_id', 
+            'author_id' => $iProfileId, 
+            'unused' => true
+        )));
+
+        $sPolls = '';
+        foreach($aPolls as $aPoll)
+            $sPolls .= $this->getPollItem($aPoll, $iProfileId, array(
+                'manage' => true
+            ));
+
+        return $this->parseHtmlByName('poll_form_field.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('add_poll_form_field'),
+            'polls' => $sPolls
+        ));
+    }
+
+    public function getPollItem($mixedPoll, $iProfileId = 0, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aPoll = is_array($mixedPoll) ? $mixedPoll : $this->_oDb->getPolls(array('type' => 'id', 'id' => (int)$mixedPoll));
+        if(empty($aPoll) || !is_array($aPoll))
+            return '';
+
+        $sJsObject = $this->_oConfig->getJsObject('poll');
+
+        $bDynamic = isset($aParams['dynamic']) && $aParams['dynamic'] === true;
+        $bManage = isset($aParams['manage']) && $aParams['manage'] === true;
+        $bSwitchMenu = isset($aParams['switch_menu']) ? (bool)$aParams['switch_menu'] : true;
+        $bForceDisplayAnswers = isset($aParams['force_display_answers']) && (bool)$aParams['force_display_answers'] === true;
+
+        $iPollId = (int)$aPoll[$CNF['FIELD_POLL_ID']];
+        $sPollView = !$bForceDisplayAnswers && $this->_oDb->isPollPerformed($iPollId, $iProfileId) ? 'results' : 'answers';
+        
+        $sMethod = '_getPoll' . ucfirst($sPollView);
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        $mixedMenu = '';
+        if($bSwitchMenu)
+            $mixedMenu = $this->_getPollBlockMenu($aPoll, $sPollView, array('template' => 'menu_interactive.html'));
+
+        return $this->parseHtmlByName('poll_item.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('poll') . $iPollId,
+            'bx_if:show_input_hidden' => array(
+                'condition' => $bManage,
+                'content' => array(
+                    'name' => $CNF['FIELD_POLL'],
+                    'id' => $iPollId
+                )
+            ),
+            'action_menu' => !empty($mixedMenu) ? $mixedMenu->getCode() : '',
+            'bx_if:show_action_embed' => array(
+                'condition' => $bManage,
+                'content' => array(
+                    'js_object' => $sJsObject,
+                    'id' => $iPollId
+                )
+            ),
+            'bx_if:show_action_delete' => array(
+                'condition' => $bManage,
+                'content' => array(
+                    'js_object' => $sJsObject,
+                    'id' => $iPollId
+                )
+            ),
+            'text' => bx_process_output($aPoll[$CNF['FIELD_POLL_TEXT']], BX_DATA_HTML),
+            'content' => $this->$sMethod($aPoll, $bDynamic)
+        ));
+    }
+
+    public function embedPollItem($mixedPoll, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $sHeader = '';
+        $sContent = $this->getPollItem($mixedPoll, 0, $aParams);
+        if(!empty($sContent)) {
+            $aPoll = is_array($mixedPoll) ? $mixedPoll : $this->_oDb->getPolls(array('type' => 'id', 'id' => (int)$mixedPoll));
+
+            $sHeader = strmaxtextlen($aPoll[$CNF['FIELD_POLL_TEXT']], 32, '...');
+            $sContent = $this->getJsCode('poll') . $sContent;
+        }
+
+        $this->addJs(array('polls.js'));
+        $this->addCss(array('polls.css'));
+
+        $oTemplate = BxDolTemplate::getInstance();
+        $oTemplate->addCssStyle($CNF['STYLES_POLLS_EMBED_CLASS'], $CNF['STYLES_POLLS_EMBED_CONTENT']);
+        $oTemplate->setPageNameIndex(BX_PAGE_EMBED);
+        $oTemplate->setPageHeader($sHeader);
+        $oTemplate->setPageContent('page_main_code', $sContent);
+        $oTemplate->getPageCode();
+        exit;
+    }
+
+    public function embedPollItems($mixedContentInfo, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aContentInfo = is_array($mixedContentInfo) ? $mixedContentInfo : $this->_oDb->getContentInfoById((int)$mixedContentInfo);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return;
+
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+
+        $aPolls = $this->_oDb->getPolls(array('type' => 'content_id', 'content_id' => $iContentId));
+        if(empty($aPolls) || !is_array($aPolls))
+            return;
+
+        $iPolls = 0;
+        $sContent = '';
+        foreach($aPolls as $aPoll) {
+            $sPoll = $this->getPollItem($aPoll, 0, $aParams);
+            if(empty($sPoll))
+                continue;
+
+            $sContent .= $sPoll;
+            $iPolls += 1;
+        }
+
+        if(!empty($sContent) && isset($aParams['showcase']) && (bool)$aParams['showcase'] === true) {
+            $this->addJs(array('flickity/flickity.pkgd.min.js'));
+            $this->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flickity/|flickity.css');
+
+            $sContent = $this->parseHtmlByName('poll_items_showcase.html', array(
+                'js_object' => $this->_oConfig->getJsObject('poll'),
+                'html_id' => $this->_oConfig->getHtmlIds('polls_showcase') . $iContentId,
+                'type' => $iPolls == 1 ? 'single' : 'multiple',
+                'polls' => $sContent
+            ));
+        }
+
+        $sHeader = '';
+        if(!empty($sContent)) {
+            $sHeader = strmaxtextlen($aContentInfo[$CNF['FIELD_TITLE']], 32, '...');
+            $sContent = $this->getJsCode('poll') . $sContent;
+        }
+
+        $this->addJs(array('polls.js'));
+        $this->addCss(array('polls.css'));
+
+        $oTemplate = BxDolTemplate::getInstance();
+        $oTemplate->addCssStyle($CNF['STYLES_POLLS_EMBED_CLASS'], $CNF['STYLES_POLLS_EMBED_CONTENT']);
+        $oTemplate->setPageNameIndex(BX_PAGE_EMBED);
+        $oTemplate->setPageHeader($sHeader);
+        $oTemplate->setPageContent('page_main_code', $sContent);
+        $oTemplate->getPageCode();
+        exit;
+    }
+
+    protected function _getPollAnswers($aPoll, $bDynamic = false)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        
+        $aAnswers = $this->_oDb->getPollAnswers(array('type' => 'poll_id', 'poll_id' => $aPoll[$CNF['FIELD_POLL_ID']]));
+        if(empty($aAnswers) || !is_array($aAnswers))
+            return '';
+
+        $aTmplVarsAnswers = array();
+        foreach($aAnswers as $aAnswer) {
+            $oVotes = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_POLL_ANSWERS'], $aAnswer['id']);
+
+            $aTmplVarsAnswers[] = array(
+                'answer' => $oVotes->getElementBlock(array(
+                    'dynamic_mode' => $bDynamic
+                ))
+            );
+        }
+
+    	return $this->parseHtmlByName('poll_item_answers.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('poll_content') . $aPoll[$CNF['FIELD_POLL_ID']],
+            'bx_repeat:answers' => $aTmplVarsAnswers
+        ));
+    }
+    
+    protected function _getPollResults($aPoll, $bDynamic = false)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aAnswers = $this->_oDb->getPollAnswers(array('type' => 'poll_id', 'poll_id' => $aPoll[$CNF['FIELD_POLL_ID']]));
+        if(empty($aAnswers) || !is_array($aAnswers))
+            return '';
+
+        $iTotal = 0;
+        foreach($aAnswers as $aAnswer)
+            $iTotal += $aAnswer['votes'];
+
+        $aTmplVarsAnswers = array();
+        foreach($aAnswers as $aAnswer) {
+            $oVotes = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_POLL_ANSWERS'], $aAnswer['id']);
+
+            $fPercent = $iTotal > 0 ? 100 * (float)$aAnswer['votes']/$iTotal : 0;
+            $aTmplVarsAnswers[] = array(
+                'title' => bx_process_output($aAnswer['title']),
+                'width' => (int)round($fPercent) . '%',
+                'votes' => $oVotes->getCounter(array('show_counter_empty' => true, 'show_counter_in_brackets' => false)),
+                'percent' => _t($CNF['T']['txt_poll_answer_vote_percent'], $iTotal > 0 ? round($fPercent, 2) : 0),
+                'js_code' => $oVotes->getJsScript($bDynamic)
+            );
+        }
+
+        return $this->parseHtmlByName('poll_item_results.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('poll_content') . $aPoll[$CNF['FIELD_POLL_ID']],
+            'bx_repeat:answers' => $aTmplVarsAnswers,
+        ));
+    }
+
+    protected function _getPollBlockMenu($aPoll, $sSelected = '', $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $sPostfix = '-' . time() . rand(0, PHP_INT_MAX);
+        $sJsObject = $this->_oConfig->getJsObject('poll');
+        $iPollId = $aPoll[$CNF['FIELD_POLL_ID']];
+
+        $aViews = array(
+            'answers' => true, 
+            'results' => $CNF['PARAM_POLL_HIDDEN_RESULTS'] === false || $this->_oDb->isPollPerformed($iPollId, bx_get_logged_profile_id())
+        );
+
+        $aMenu = array();
+        foreach($aViews as $sView => $bActive) {
+            if(!$bActive) 
+                continue;
+
+            $sId = $this->_oConfig->getHtmlIds('poll_view_link_' . $sView) . $iPollId;
+            if(!empty($sSelected) && $sSelected == $sView)
+                $sSelected = $sId;
+
+            $aMenu[] = array('id' => $sId, 'name' => $sId, 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => 'javascript:' . $sJsObject . '.changePollView(this, \'' . $sView . '\', ' . $iPollId . ')', 'target' => '_self', 'title' => _t('_bx_posts_txt_poll_view_' . $sView));
+        }
+
+        if(count($aMenu) <= 1)
+            return '';
+
+        $oMenu = new BxTemplMenuInteractive(array(
+            'template' => !empty($aParams['template']) ? $aParams['template'] : 'menu_interactive_vertical.html', 
+            'menu_id' => $this->_oConfig->getHtmlIds('poll_view_menu') . $sPostfix, 
+            'menu_items' => $aMenu
+        ));
+
+        if(!empty($sSelected))
+            $oMenu->setSelected('', $sSelected);
+
+        return $oMenu;
+    }
+
+    public function getTmplVarsText($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aVars = parent::getTmplVarsText($aData);
+
+        $sImage = '';
+        $mixedImage = $this->_getHeaderImage($aData);
+        if($mixedImage !== false) {
+            if(!empty($mixedImage['object']))
+                $o = BxDolStorage::getObjectInstance($mixedImage['object']);
+            else if(!empty($mixedImage['transcoder']))
+                $o = BxDolTranscoder::getObjectInstance($mixedImage['transcoder']);
+
+            if($o)
+                $sImage = $o->getFileUrlById($mixedImage['id']);
+        }
+
+        $aVars['content_description_before'] = '';
+        $aVars['content_description_after'] = '';
+        $aVars['bx_if:show_image'] = array(
+            'condition' => !empty($sImage),
+            'content' => array(
+                'entry_image' => $sImage
+            )
+        );
+
+        if(!empty($CNF['OBJECT_REACTIONS'])) {
+            $oReactions = BxDolVote::getObjectInstance($CNF['OBJECT_REACTIONS'], $aData[$CNF['FIELD_ID']]);
+            if($oReactions)
+                $aVars['content_description_after'] .= $oReactions->getCounter(array(
+                    'show_counter' => true
+                ));
+        }
+
+        return $aVars;
+    }
+
+    function getAuthorDesc($aData, $oProfile)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aItem = array(
+            'bx_if:text' => array(
+                'condition' => false,
+                'content' => array(
+                    'content' => ''
+                )
+            ),
+            'bx_if:link' => array(
+                'condition' => false,
+                'content' => array(
+                    'link' => '',
+                    'content' => ''
+                )
+            )
+        );
+
+        $aTmplVarsItems = array();
+        if(!empty($CNF['FIELD_ADDED']) && !empty($aData[$CNF['FIELD_ADDED']]))
+            $aTmplVarsItems[] = array_merge($aItem, array('bx_if:text' => array(
+                'condition' => true,
+                'content' => array(
+                    'content' => bx_time_js($aData[$CNF['FIELD_ADDED']], BX_FORMAT_DATE)
+                )
+            )));
+
+        if(!empty($CNF['URI_AUTHOR_ENTRIES']))
+            $aTmplVarsItems[] = array_merge($aItem, array('bx_if:link' => array(
+                'condition' => true,
+                'content' => array(
+                    'link' => BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_AUTHOR_ENTRIES'] . '&profile_id=' . $oProfile->id()),
+                    'content' => _t($CNF['T']['txt_all_entries_by'], $this->getModule()->_oDb->getEntriesNumByAuthor($oProfile->id()))
+                )
+            )));
+
+        return $this->parseHtmlByName('author_desc.html', array(
+            'bx_repeat:items' => $aTmplVarsItems
+        ));
+    }
+
+    function getAuthorProfileDesc ($aData, $oProfile)
+    {
+        $aSnippetMeta = $this->getProfileSnippetMenu($aData);
+        if(empty($aSnippetMeta) || !is_array($aSnippetMeta) || !isset($aSnippetMeta['meta']))
+            return '';
+
+        return $aSnippetMeta['meta'];
+    }
+
+    function getContextDesc ($aData)
+    {
+        return '';
+    }
+
+    function getProfileSnippetMenu ($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        if (!($oProfile = BxDolProfile::getInstance($aData[$CNF['FIELD_AUTHOR']])))
+            return array();
+
+        return bx_srv($oProfile->getModule(), 'get_snippet_menu_vars', array($oProfile->id()));
+    }
+
+    function getAuthorAddon ($aData, $oProfile)
+    {
+        return '';
+    }
+
+    function getContextAddon ($aData, $oProfile)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        $sUrl = 'page.php?i=' . $CNF['URI_ENTRIES_BY_CONTEXT'] . '&profile_id=' . $oProfile->id();
+        $sUrl = BxDolPermalinks::getInstance()->permalink($sUrl);
+        return _t($CNF['T']['txt_all_entries_in'], $sUrl, $oProfile->getDisplayName(), $this->getModule()->_oDb->getEntriesNumByContext($oProfile->id()));
+    }
+
+    protected function checkPrivacy ($aData, $isCheckPrivateContent, $oModule, $sTemplateName = '')
+    {
+        if ($isCheckPrivateContent && CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $oModule->checkAllowedView($aData))) {
+            $aVars = array (
+                'summary' => $sMsg,
+            );
+            return $this->parseHtmlByName($sTemplateName ? str_replace('.html', '_private.html', $sTemplateName) : 'unit_private.html', $aVars);
+        }
+
+        return '';
+    }
+
+    protected function getUnitThumbAndGallery ($aData)
+    {
+        $CNF = &BxDolModule::getInstance($this->MODULE)->_oConfig->CNF;
+
+        $sPhotoThumb = '';
+        $sPhotoGallery = '';
+        if(!empty($CNF['FIELD_THUMB']) && !empty($aData[$CNF['FIELD_THUMB']])) {
+
+            $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+            if ($oImagesTranscoder)
+                $sPhotoThumb = $oImagesTranscoder->getFileUrl($aData[$CNF['FIELD_THUMB']]);
+
+            $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY']);
+            if ($oImagesTranscoder)
+                $sPhotoGallery = $oImagesTranscoder->getFileUrl($aData[$CNF['FIELD_THUMB']]);
+            else
+                $sPhotoGallery = $sPhotoThumb;
+        }
+
+        return array($sPhotoThumb, $sPhotoGallery);
+    }
+
+    protected function getUnit ($aData, $aParams = array())
+    {
+        $CNF = &BxDolModule::getInstance($this->MODULE)->_oConfig->CNF;
+
+        // get thumb url
+        list($sPhotoThumb, $sPhotoGallery) = $this->getUnitThumbAndGallery($aData);
+
+        // get entry url
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aData[$CNF['FIELD_ID']]);
+
+        $oProfile = BxDolProfile::getInstanceMagic($aData[$CNF['FIELD_AUTHOR']]);
+        
+        $sTitle = $this->getTitle($aData);
+        $sText = $this->getText($aData);
+        $sSummary = $this->getSummary($aData, $sTitle, $sText, $sUrl);
+        $sSummaryPlain = isset($CNF['PARAM_CHARS_SUMMARY_PLAIN']) && $CNF['PARAM_CHARS_SUMMARY_PLAIN'] ? BxTemplFunctions::getInstance()->getStringWithLimitedLength(strip_tags($sSummary), (int)getParam($CNF['PARAM_CHARS_SUMMARY_PLAIN'])) : '';
+
+        if(!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $sText = $oMetatags->metaParse($aData[$CNF['FIELD_ID']], $sText);
+        }
+
+        $sTitle = bx_process_output($sTitle);
+
+        $aTmplVarsMeta = array();
+        if(!empty($CNF['OBJECT_MENU_SNIPPET_META'])) {
+            $oMenuMeta = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SNIPPET_META'], $this);
+            if($oMenuMeta) {
+                $oMenuMeta->setContentId($aData[$CNF['FIELD_ID']]);
+                $aTmplVarsMeta = array(
+                    'meta' => $oMenuMeta->getCode()
+                );
+            }
+        }
+
+        // generate html
+        return array (
+            'class' => $this->_getUnitClass($aData,(isset($aParams['template_name']) ? $aParams['template_name'] : '')),
+            'id' => $aData[$CNF['FIELD_ID']],
+            'content_url' => $sUrl,
+            'title' => $sTitle,
+            'title_attr' => bx_html_attribute($sTitle),
+            'summary' => $sSummary,
+            'text' => $sText,
+            'author' => $oProfile->getDisplayName(),
+            'author_url' => $oProfile->getUrl(),
+            'author_icon' => $oProfile->getIcon(),
+            'author_thumb' => $oProfile->getThumb(),
+            'author_avatar' => $oProfile->getAvatar(),
+            'entry_posting_date' => bx_time_js($aData[$CNF['FIELD_ADDED']], BX_FORMAT_DATE),
+            'module_name' => _t($CNF['T']['txt_sample_single']),
+            'ts' => $aData[$CNF['FIELD_ADDED']],
+            'bx_if:meta' => array(
+                'condition' => !empty($aTmplVarsMeta),
+                'content' => $aTmplVarsMeta
+            ),
+            'bx_if:thumb' => array (
+                'condition' => $sPhotoThumb,
+                'content' => array (
+                    'title' => $sTitle,
+                    'summary_attr' => bx_html_attribute($sSummaryPlain),
+                    'content_url' => $sUrl,
+                    'thumb_url' => $sPhotoThumb ? $sPhotoThumb : '',
+                    'gallery_url' => $sPhotoGallery ? $sPhotoGallery : '',
+                    'strecher' => str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', 40),
+                ),
+            ),
+            'bx_if:no_thumb' => array (
+                'condition' => !$sPhotoThumb,
+                'content' => array (
+                    'module_icon' => $CNF['ICON'],
+                    'content_url' => $sUrl,
+                    'summary_plain' => $sSummaryPlain,
+                    'strecher' => mb_strlen($sSummaryPlain) > 240 ? '' : str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ', round((240 - mb_strlen($sSummaryPlain)) / 6)),
+                ),
+            ),
+        );
+    }
+
+    protected function getTitle($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        return isset($aData[$CNF['FIELD_TITLE']]) ? $aData[$CNF['FIELD_TITLE']] : '';
+    }
+
+    protected function getText($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        return isset($aData[$CNF['FIELD_TEXT']]) ? $aData[$CNF['FIELD_TEXT']] : '';
+    }
+
+    protected function getSummary($aData, $sTitle = '', $sText = '', $sUrl = '')
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        if(empty($CNF['PARAM_CHARS_SUMMARY']))
+            return '';
+
+        // get summary
+        $sLinkMore = ' <a title="' . bx_html_attribute(_t('_sys_read_more', $sTitle)) . '" href="' . $sUrl . '"><i class="sys-icon ellipsis-h"></i></a>';
+        return  strmaxtextlen($sText, (int)getParam($CNF['PARAM_CHARS_SUMMARY']), $sLinkMore);
+    }
+
+    protected function getAttachmentsImagesTranscoders ($sStorage = '')
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $oTranscoder = null;
+        $oTranscoderView = null;
+
+        if(isset($CNF['OBJECT_STORAGE_PHOTOS']) && $CNF['OBJECT_STORAGE_PHOTOS'] == $sStorage) {
+            if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_PHOTOS']))
+                $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_PHOTOS']);
+            if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY_PHOTOS']))
+                $oTranscoderView = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY_PHOTOS']);
+        }
+        else if(isset($CNF['OBJECT_STORAGE_FILES']) && $CNF['OBJECT_STORAGE_FILES'] == $sStorage) {
+            if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_FILES']))
+                $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_FILES']);
+            if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY_FILES']))
+                $oTranscoderView = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY_FILES']);
+        }
+        else
+            list($oTranscoder, $oTranscoderView) = parent::getAttachmentsImagesTranscoders($sStorage);
+
+        return array($oTranscoder, $oTranscoderView);
+    }
+
+    protected function _getUnitClass($aData, $sTemplateName = 'unit.html')
+    {
+        $sResult = '';
+
+        switch($sTemplateName) {
+            case 'unit_showcase.html':
+                $sResult = $this->_sUnitClassShowCase;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    protected function _getHeaderImage($aData)
+    {
+        return $this->getModule()->getEntryImageData($aData);
+    }
+}
+
+/** @} */

+ 275 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/template/css/polls.css

@@ -0,0 +1,275 @@
+/*-- Poll form: Answers custom element --*/
+.bx-form-input-answer {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.bx-form-input-answer.bx-fi-answer-blank {
+    display: none;
+}
+.bx-form-input-answer-cell.bx-fi-sc-txt {
+    -webkit-flex: 1 1 auto;
+    -ms-flex: 1 1 auto;
+    flex: 1 1 auto;
+}
+.bx-form-input-answer-cell.bx-fi-sc-btn {
+    -webkit-flex: 0 0 auto;
+    -ms-flex: 0 0 auto;
+    flex: 0 0 auto;
+}
+.bx-form-input-answer-add {
+    text-align: right;
+}
+.bx-form-input-answer-add .bx-form-input-button {
+    display: inline-block;
+    float: none;
+}
+
+
+/*--- Polls list ---*/
+.bx-base-text-polls {
+    position: relative;
+
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+}
+
+.bx-base-text-polls .bx-base-text-poll {
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+
+    -webkit-box-flex: 0;
+
+    -webkit-flex: 0 0 100%;
+    -ms-flex: 0 0 100%;
+    flex: 0 0 100%;
+
+    overflow:hidden;
+}
+
+.bx-base-text-polls-showcase {
+    position: relative;
+}
+
+.bx-base-text-polls-showcase .flickity-slider {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+
+.bx-base-text-polls-showcase .bx-base-text-poll {
+    position: relative;
+    width: 100%;
+}
+
+/* Media: phone2 */
+.bx-media-phone2 .bx-layout-column-half .bx-base-text-polls.bx-base-text-autosizing .bx-base-text-poll,
+.bx-media-phone2 .bx-layout-column-thin .bx-base-text-polls.bx-base-text-autosizing .bx-base-text-poll {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+
+/* Media: tablet2 */
+.bx-media-tablet2 .bx-layout-column-wide .bx-base-text-polls.bx-base-text-autosizing .bx-base-text-poll,
+.bx-media-tablet2 .bx-layout-column-fullwidth .bx-base-text-polls.bx-base-text-autosizing .bx-base-text-poll {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+
+/* Media: desktop */
+.bx-media-desktop .bx-layout-column-wide .bx-base-text-polls.bx-base-text-autosizing .bx-base-text-poll {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+.bx-media-desktop .bx-layout-column-fullwidth .bx-base-text-polls.bx-base-text-autosizing .bx-base-text-poll {
+    -webkit-flex: 0 0 33.25%;
+    -ms-flex: 0 0 33.25%;
+    flex: 0 0 33.25%;
+}
+
+
+/*--- Poll item ---*/
+.bx-base-text-poll {
+    position: relative;
+}
+.bx-base-text-poll-cnt {
+    position: relative;
+    width: 100%;
+}
+.bx-base-text-poll-question p {
+    margin: 0;
+    padding: 0;
+}
+
+/*+++ Poll item: Actions +++*/
+.bx-base-text-poll-actions {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+    -webkit-justify-content: flex-end;
+    justify-content: flex-end;
+    -webkit-align-items: center;
+    align-items: center;
+
+    top: 0;
+    right: 0;
+}
+.bx-base-text-poll-action {
+    position: relative;
+    display: block;
+}
+.bx-base-text-poll-actions li.bx-def-color-bg-hl-hover {
+    backgroud-color: transparent;
+}
+.bx-base-text-poll-actions li.bx-menu-inter-act,
+.bx-base-text-poll-actions li.bx-menu-inter-pas {
+    margin-left: 0.5rem;
+}
+.bx-base-text-poll-actions li.bx-menu-inter-act div, 
+.bx-base-text-poll-actions li.bx-menu-inter-pas div{
+    padding: 0;
+}
+.bx-base-text-poll-actions li.bx-menu-inter-div {
+    display: none;
+}
+
+/*+++ Poll item: Results +++*/
+div.bx-base-text-pr-graph {
+	display: table;
+	width: 100%;
+}
+div.bx-base-text-pr-graph .bx-base-text-prg-bar,
+div.bx-base-text-pr-graph .bx-base-text-prg-text {
+	display: table-cell;
+}
+div.bx-base-text-pr-graph .bx-base-text-prg-bar {
+    -webkit-animation-fill-mode: both;
+    -moz-animation-fill-mode: both;
+    -o-animation-fill-mode: both;
+    animation-fill-mode: both;
+
+    -webkit-transform-origin: left;
+    -moz-transform-origin: left;
+    -ms-transform-origin: left;
+    -o-transform-origin: left;
+    transform-origin: left;
+
+    -webkit-animation: bx-base-text-prg-bar-anim 1000ms ease-out;
+    -o-animation: bx-base-text-prg-bar-anim 1000ms ease-out;
+    animation: bx-base-text-prg-bar-anim 1000ms ease-out;
+
+    background: #2196F3;
+    background: -moz-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: -webkit-gradient(linear,left top,right top,color-stop(0%,#2196F3),color-stop(100%,#1976D2));
+    background: -webkit-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: -o-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: -ms-linear-gradient(left,#2196F3 0%,#1976D2 100%);
+    background: linear-gradient(to right,#2196F3 0%,#1976D2 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#2196F3',endColorstr='#1976D2',GradientType=1);
+}
+div.bx-base-text-pr-graph .bx-base-text-prg-text {
+	white-space: nowrap;
+}
+
+/*--- Poll item embed ---*/
+.bx-base-text-poll-embed {
+    width: 100%;
+    height: 240px;
+    margin-left: -0.5rem;
+    margin-right: -0.5rem;
+}
+
+.bx-page-iframe .bx-base-text-poll {
+    width: 80vw;
+    height: 100vh;
+}
+.bx-page-iframe .bx-base-text-showcase-type-single .bx-base-text-poll {
+    width: 100vw;
+}
+.bx-page-iframe .bx-base-text-poll-cnt {
+    height: 100%;
+    box-sizing: border-box;
+}
+.bx-page-iframe .bx-base-text-poll-plate {
+    height: 100%;
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+
+/*--- Animation ---*/
+@-webkit-keyframes bx-base-text-prg-bar-anim {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}
+@-moz-keyframes bx-base-text-prg-bar-anim {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}
+@-ms-keyframes bx-base-text-prg-bar-anim {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}
+@keyframes bx-base-text-prg-bar-anim {
+    from {
+        -webkit-transform:scaleX(0);
+        -moz-transform:scaleX(0);
+        -ms-transform:scaleX(0);
+        -o-transform:scaleX(0);
+        transform:scaleX(0)
+    }
+    to {
+        -webkit-transform:scaleX(1);
+        -moz-transform:scaleX(1);
+        -ms-transform:scaleX(1);
+        -o-transform:scaleX(1);
+        transform:scaleX(1)
+    }
+}

+ 6 - 0
upgrade/files/10.0.0-10.1.0.B1/files/modules/base/text/template/poll_items_showcase.html

@@ -0,0 +1,6 @@
+<div id="__html_id__" class="bx-base-text-polls-showcase bx-base-text-showcase-type-__type__">__polls__</div>
+<script>
+    $(document).ready(function() {
+        __js_object__.initFlickity('#__html_id__');
+    });
+</script>

+ 8 - 0
upgrade/files/10.0.0-10.1.0.B1/files/plugins_public/onesignal/OneSignalSDKUpdaterWorker.js.php

@@ -0,0 +1,8 @@
+<?php
+/**
+ * Note: This file is intended to be publicly accessible.
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
+ */
+header("Service-Worker-Allowed: /");
+header("Content-Type: application/javascript");
+echo "importScripts('https://cdn.onesignal.com/sdks/OneSignalSDKWorker.js');";

+ 9 - 0
upgrade/files/10.0.0-10.1.0.B1/files/plugins_public/onesignal/OneSignalSDKWorker.js.php

@@ -0,0 +1,9 @@
+<?php
+/**
+ * Note: This file is intended to be publicly accessible.
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
+ */
+header("Service-Worker-Allowed: /");
+header("Content-Type: application/javascript");
+header("X-Robots-Tag: none");
+echo "importScripts('https://cdn.onesignal.com/sdks/OneSignalSDKWorker.js');";

+ 195 - 0
upgrade/files/10.0.0-10.1.0.B1/files/studio/classes/BxDolStudioMenuTop.php

@@ -0,0 +1,195 @@
+<?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
+ * @{
+ */
+
+require_once(BX_DOL_DIR_STUDIO_INC . 'utils.inc.php');
+
+define('BX_DOL_STUDIO_MT_LEFT', 'left');
+define('BX_DOL_STUDIO_MT_CENTER', 'center');
+define('BX_DOL_STUDIO_MT_RIGHT', 'right');
+
+define('BX_DOL_STUDIO_MTB_CIRCLE', 'circ');
+define('BX_DOL_STUDIO_MTB_RECTANGLE', 'rect');
+
+class BxDolStudioMenuTop extends BxDol
+{
+    public static $sHistorySessionKey = 'sys_studio_history';
+    public static $iHistoryLength = 5;
+
+    protected $aItems;
+    protected $aVisible;
+    protected $aSelected;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->aVisible = array(
+            BX_DOL_STUDIO_MT_LEFT => true,
+            BX_DOL_STUDIO_MT_CENTER => true,
+            BX_DOL_STUDIO_MT_RIGHT => true
+        );
+
+        $this->aSelected = array(
+            BX_DOL_STUDIO_MT_LEFT => '',
+            BX_DOL_STUDIO_MT_CENTER => '',
+            BX_DOL_STUDIO_MT_RIGHT => ''
+        );
+
+        $this->aItems = array(
+            BX_DOL_STUDIO_MT_LEFT => '',
+            BX_DOL_STUDIO_MT_CENTER => '',
+            BX_DOL_STUDIO_MT_RIGHT => ''
+        );
+
+        $this->aItems[BX_DOL_STUDIO_MT_LEFT] = array(
+            'template' => 'menu_floating_blocks.html',
+            'menu_items' => array(
+                'launcher' => array(
+                    'name' => 'launcher',
+                    'icon' => 'th',
+                    'link' => BX_DOL_URL_STUDIO,
+                    'title' => '_adm_tmi_cpt_launcher'
+                ),
+                'site' => array(
+                    'name' => 'site',
+                    'icon' => 'home',
+                    'link' => BX_DOL_URL_ROOT,
+                    'title' => '_adm_tmi_cpt_site'
+                ),
+                'logout' => array(
+                    'name' => 'logout',
+                    'icon' => 'sign-out-alt',
+                    'link' => BX_DOL_URL_ROOT . 'logout.php',
+                    'onclick' => $this->getJsObject() . '.clickLogout(this);',
+                    'title' => '_adm_tmi_cpt_logout'
+                )
+            )
+        );
+
+        $aHistory = self::historyGetList();
+        if(!empty($aHistory) && is_array($aHistory))
+            $this->aItems[BX_DOL_STUDIO_MT_LEFT]['menu_items'] = bx_array_insert_before(array_reverse($aHistory), $this->aItems[BX_DOL_STUDIO_MT_LEFT]['menu_items'], 'site');
+
+        $aMatch = array();
+        $iResult = preg_match("/^(https?:\/\/)?(.*)\/$/", BX_DOL_URL_ROOT, $aMatch);
+        $this->aItems[BX_DOL_STUDIO_MT_CENTER] = $iResult ? $aMatch[2] : '';
+
+        $this->aItems[BX_DOL_STUDIO_MT_RIGHT] = array(
+            'template' => 'menu_top_toolbar.html',
+            'menu_items' => array(
+                'edit' => array(
+                    'name' => 'edit',
+                    'icon' => 'magic',
+                    'onclick' => $this->getJsObject() . '.clickEdit(this);',
+                    'title' => '_adm_tmi_cpt_edit'
+                ),
+                'favorite' => array(
+                    'name' => 'favorite',
+                    'icon' => 'star',
+                    'onclick' => $this->getJsObject() . '.clickFavorite(this);',
+                    'title' => '_adm_tmi_cpt_favorite'
+                ),
+                'extensions' => array(
+                    'name' => 'extensions',
+                    'icon' => 'plus',
+                    'link' => BX_DOL_URL_STUDIO . 'store.php?page=goodies',
+                    'title' => '_adm_tmi_cpt_extensions'
+                ),
+                'tour' => array(
+                    'name' => 'tour',
+                    'icon' => 'question',
+                    'link' => 'javascript:void(0);',
+                    'onclick' => 'glTour.start()',
+                    'title' => '_adm_tmi_cpt_tour'
+                )
+            )
+        );
+
+        $aLanguages = BxDolLanguagesQuery::getInstance()->getLanguages(false, true);
+        if(count($aLanguages) > 1)
+            $this->aItems[BX_DOL_STUDIO_MT_RIGHT]['menu_items']['language'] = array(
+                'name' => 'language',
+                'icon' => 'language',
+                'link' => 'javascript:void(0);',
+                'onclick' => "bx_menu_popup('sys_switch_language_popup', this);",
+                'title' => '_adm_tmi_cpt_language'
+            );
+    }
+
+    public static function historyGetList()
+    {
+        return BxDolSession::getInstance()->getValue(self::$sHistorySessionKey);
+    }
+    
+    public static function historyAdd($aPage)
+    {
+        $oSession = BxDolSession::getInstance();
+        $aHistory = $oSession->getValue(self::$sHistorySessionKey);
+        if(!empty($aHistory) && isset($aHistory[$aPage['name']]))
+            return;
+
+        $aHistory[$aPage['name']] = array(
+            'name' => $aPage['name'],
+            'icon' => $aPage['wid_icon'],
+            'link' => $aPage['wid_url'],
+            'onclick' => $aPage['wid_click'],
+            'title' => $aPage['wid_caption']
+        );
+        if(count($aHistory) > BxTemplStudioMenuTop::$iHistoryLength)
+            $aHistory = array_slice($aHistory, -BxTemplStudioMenuTop::$iHistoryLength);
+        $oSession->setValue(self::$sHistorySessionKey, $aHistory);
+    }
+
+    public static function historyDelete($mixedPage)
+    {
+        if(is_array($mixedPage)) {
+            if(!isset($mixedPage['name']))
+                return;
+
+            $mixedPage = $mixedPage['name'];
+        }
+
+        $oSession = BxDolSession::getInstance();
+        $aHistory = $oSession->getValue(self::$sHistorySessionKey);
+        if(empty($aHistory) || !isset($aHistory[$mixedPage]))
+            return;
+
+        unset($aHistory[$mixedPage]);
+        $oSession->setValue(self::$sHistorySessionKey, $aHistory);
+    }
+
+    public function setContent($sPosition, $mixedContent)
+    {
+        $this->aItems[$sPosition] = $mixedContent;
+    }
+
+    public function setSelected($sPosition, $sValue)
+    {
+        if(!isset($this->aItems[$sPosition]['menu_items'][$sValue]))
+            return;
+
+        $this->aItems[$sPosition]['menu_items'][$sValue]['selected'] = true;
+    }
+
+    public function setVisible($sPosition, $bValue = true)
+    {
+        $this->aVisible[$sPosition] = $bValue;
+    }
+
+    public function setVisibleAll($bValue = true)
+    {
+        $this->aVisible = array(
+            BX_DOL_STUDIO_MT_LEFT => $bValue,
+            BX_DOL_STUDIO_MT_CENTER => $bValue,
+            BX_DOL_STUDIO_MT_RIGHT => $bValue
+        );
+    }
+}
+
+/** @} */

+ 24 - 0
upgrade/files/10.0.0-10.1.0.B1/files/studio/template/css/menu.css

@@ -0,0 +1,24 @@
+@import url(../../../template/css/menu.css);
+
+
+/* --------------- toolbar menu */
+ul.bx-menu-toolbar li .sys-icon-a,
+ul.bx-menu-toolbar li .sys-icon {
+    font-size: 1.5rem;
+    width: 1.5rem;
+    height: 1.5rem;
+}
+ul.bx-menu-toolbar li a .sys-icon-a {
+    margin-top:0.5rem;
+}
+
+
+/* --------------- vertical, vertical lite menu */
+.bx-popup .bx-menu-ver li div {
+    padding: 10px 20px 10px 10px;
+    padding: 0.625rem 1.25rem 0.625rem 0.625rem;
+}
+
+.bx-popup .bx-menu-ver li div > span {
+    white-space: nowrap;
+}

+ 107 - 0
upgrade/files/10.0.0-10.1.0.B1/files/studio/template/scripts/BxBaseStudioLauncher.php

@@ -0,0 +1,107 @@
+<?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
+ * @{
+ */
+
+define('BX_DOL_STUDIO_LAUNCHER_JS_OBJECT', 'oBxDolStudioLauncher');
+
+class BxBaseStudioLauncher extends BxDolStudioLauncher
+{
+    protected $_sTourTheme = 'default';
+
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function getPageIndex()
+    {
+        if(!is_array($this->aPage || empty($this->aPage)))
+            return BX_PAGE_DEFAULT;
+
+        return !empty($this->aPage[$this->sPageSelected]) ? $this->aPage[$this->sPageSelected]->getPageIndex() : BX_PAGE_DEFAULT;
+    }
+
+    public function getPageJs()
+    {
+        $aJs = array(
+            'jquery-ui/jquery.ui.core.min.js',
+            'jquery-ui/jquery.ui.widget.min.js',
+            'jquery-ui/jquery.ui.mouse.min.js',
+            'jquery-ui/jquery.ui.sortable.min.js',
+            'jquery.ui.touch-punch.min.js',
+            'jquery.easing.js',
+            'jquery.cookie.min.js',
+            'launcher.js',
+            'shepherd/js/tether.min.js',
+            'shepherd/js/shepherd.min.js',
+        );
+        foreach($this->aIncludes as $sName => $oInclude)
+            $aJs = array_merge($aJs, $oInclude->getJs());
+
+        return $aJs;
+    }
+
+    public function getPageJsObject()
+    {
+        return BX_DOL_STUDIO_LAUNCHER_JS_OBJECT;
+    }
+
+    public function getPageCss()
+    {
+        $aCss = array(
+            'launcher.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flag-icon-css/css/|flag-icon.min.css',
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'shepherd/css/|shepherd-theme-' . $this->_sTourTheme . '.css',
+        );
+        foreach($this->aIncludes as $sName => $oInclude)
+            $aCss = array_merge($aCss, $oInclude->getCss());
+
+        return $aCss;
+    }
+
+    public function getPageCode($bHidden = false)
+    {
+        if(empty($this->aPage) || !is_array($this->aPage))
+            return '';
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sIncludes = '';
+        foreach($this->aIncludes as $sName => $oInclude)
+            $sIncludes .= $oInclude->getJsCode();
+
+        $s = $oTemplate->parseHtmlByName('launcher.html', array(
+            'js_object' => $this->getPageJsObject(),
+            'includes' => $sIncludes,
+            'items' => parent::getPageCode($bHidden),
+        ));
+
+        if (getParam('site_tour_studio') == 'on')
+            $s .= $oTemplate->parseHtmlByName('launcher_tour.html', array('tour_theme' => $this->_sTourTheme));
+
+        return $s;
+    }
+
+    public function serviceGetCacheUpdater()
+    {
+        check_logged();
+        if(!isAdmin())
+            return '';
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+        $sContent = $oTemplate->addJs('launcher.js', true);
+        $sContent .= $oTemplate->parseHtmlByName('launcher_cache_updater.html', array(
+            'js_object' => $this->getPageJsObject()
+        ));
+
+        return $sContent;
+    }
+}
+
+/** @} */

+ 333 - 0
upgrade/files/10.0.0-10.1.0.B1/files/studio/template/scripts/BxBaseStudioPage.php

@@ -0,0 +1,333 @@
+<?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 BxBaseStudioPage extends BxDolStudioPage
+{
+    function __construct($mixedPageName)
+    {
+        parent::__construct($mixedPageName);
+    }
+
+    public function getPageIndex()
+    {
+        if(!is_array($this->aPage) || empty($this->aPage))
+            return BX_PAGE_DEFAULT;
+
+        if(!$this->bPageMultiple)
+            return !empty($this->aPage['index']) ? (int)$this->aPage['index'] : BX_PAGE_DEFAULT;
+        else
+            return !empty($this->aPage[$this->sPageSelected]['index']) ? (int)$this->aPage[$this->sPageSelected]['index'] : BX_PAGE_DEFAULT;
+    }
+
+    public function getPageJs()
+    {
+        return array('jquery.anim.js', 'jquery.jfeed.pack.js', 'jquery.dolRSSFeed.js', 'page.js');
+    }
+
+    public function getPageJsClass()
+    {
+        return '';
+    }
+
+    public function getPageJsObject()
+    {
+        return '';
+    }
+
+    public function getPageJsCode($aOptions = array(), $bWrap = true)
+    {
+        $sJsClass = $this->getPageJsClass();
+        $sJsObject = $this->getPageJsObject();
+        if(empty($sJsClass) || empty($sJsObject))
+            return '';
+
+        $sOptions = '{}';
+        if(!empty($aOptions))
+            $sOptions = json_encode($aOptions);
+
+        $sContent = 'var ' . $sJsObject . ' = new ' . $sJsClass . '(' . $sOptions . ');';
+        if($bWrap)
+            $sContent = BxDolStudioTemplate::getInstance()->_wrapInTagJsCode($sContent);
+
+        return $sContent;
+    }
+
+    public function getPageCss()
+    {
+        $aCss = array('page.css', 'page-media-tablet.css', 'page-media-desktop.css', 'menu_top.css');
+        if((int)$this->aPage['index'] == 3)
+            $aCss[] = 'page_columns.css';
+
+        return $aCss;
+    }
+
+    public function getPageHeader()
+    {
+        if(empty($this->aPage) || !is_array($this->aPage))
+            return '';
+
+        return _t(!$this->bPageMultiple ? $this->aPage['caption'] : $this->aPage[$this->sPageSelected]['caption']);
+    }
+
+    public function getPageBreadcrumb()
+    {
+        return array();
+    }
+
+    public function getPageCaption()
+    {
+        if(empty($this->aPage) || !is_array($this->aPage))
+            return '';
+
+        $this->updateHistory();
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+        $oFunctions = BxTemplStudioFunctions::getInstance();
+
+        $bActions = false;
+        $sActions = $this->getPageCaptionActions();
+        if(($bActions = strlen($sActions)) > 0)
+            $sActions = $oFunctions->transBox('bx-std-pcap-menu-popup-actions', $sActions, true);
+
+        $bHelp = false;
+        $sHelp = $this->getPageCaptionHelp();
+        if(($bHelp = strlen($sHelp)) > 0)
+            $sHelp = $oFunctions->transBox('bx-std-pcap-menu-popup-help', $sHelp, true);
+
+        $oTemplate->addInjection('injection_header', 'text', $sActions . $sHelp);
+
+        //--- Menu Right ---//
+        $aItemsRight = array();
+
+        if($bActions)
+            $aItemsRight['actions'] = array(
+                'name' => 'actions',
+                'icon' => 'cog',
+                'onclick' => BX_DOL_STUDIO_PAGE_JS_OBJECT . ".togglePopup('actions', this)",
+                'title' => '_adm_txt_show_actions'
+            );
+
+        if($bHelp)
+            $aItemsRight['help'] = array(
+                'name' => 'help',
+                'icon' => 'question-circle',
+                'onclick' => BX_DOL_STUDIO_PAGE_JS_OBJECT . ".togglePopup('help', this)",
+                'title' => '_adm_txt_show_help'
+            );
+
+        $aLanguages = BxDolLanguagesQuery::getInstance()->getLanguages(false, true);
+        if(count($aLanguages) > 1)
+            $aItemsRight['language'] = array(
+                'name' => 'language',
+                'icon' => 'language',
+                'onclick' => "bx_menu_popup('sys_switch_language_popup', this);",
+                'title' => '_adm_tmi_cpt_language'
+            );
+
+        $oTopMenu = BxTemplStudioMenuTop::getInstance();
+        $oTopMenu->setSelected(BX_DOL_STUDIO_MT_LEFT, $this->aPage['name']);
+        $oTopMenu->setContent(BX_DOL_STUDIO_MT_CENTER, _t($this->aPage['caption']));
+        $oTopMenu->setContent(BX_DOL_STUDIO_MT_RIGHT, array('template' => 'menu_top_toolbar.html', 'menu_items' => $aItemsRight));
+
+        return '';
+    }
+
+    public function getPageAttributes()
+    {
+        return '';
+    }
+
+    public function getPageMenu($aMenu, $aMarkers = array())
+    {
+        $oMenu = new BxTemplStudioMenu(array('template' => 'menu_side.html', 'menu_items' => $aMenu));
+        if(!empty($aMarkers))
+            $oMenu->addMarkers($aMarkers);
+
+        return $oMenu->getCode();
+    }
+
+    public function getPageCode($bHidden = false) {}
+
+    protected function getPageCaptionHelp()
+    {
+    	$sContent = BxDolRss::getObjectInstance($this->sPageRssHelpObject)->getHolder($this->sPageRssHelpId, $this->iPageRssHelpLength, 0, false);
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+    	$oTemplate->addJsTranslation('_adm_txt_show_help_content_empty');
+        return $oTemplate->parseHtmlByName('page_caption_help.html', array(
+            'content' => $sContent
+        ));
+    }
+
+    protected function getPageCaptionActions()
+    {
+        if(empty($this->aActions))
+            return "";
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-page-actions',
+                'name' => 'adm-page-actions',
+                'action' => '',
+                'method' => 'post',
+            ),
+            'params' => array(),
+            'inputs' => array()
+        );
+
+        foreach($this->aActions as $aAction) {
+            $aInput = array(
+                'type' => $aAction['type'],
+                'name' => $aAction['name'],
+                'caption' => _t($aAction['caption'])
+            );
+
+            switch($aAction['type']) {
+                case 'switcher':
+                    $aInput['checked'] = $aAction['checked'];
+                    $aInput['attrs']['onchange'] = $aAction['onchange'];
+                    break;
+
+            }
+
+            $aForm['inputs'][$aInput['name']] = $aInput;
+        }
+
+        $oForm = new BxTemplStudioFormView($aForm);
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('page_caption_actions.html', array('content' => $oForm->getCode()));
+    }
+
+    /**
+     *
+     * Block Methods
+     *
+     */
+    public function getBlocksLine($aBlocks)
+    {
+        $aTmplVarsBlocks = array();
+        foreach ($aBlocks as $aBlock) {
+            $aTmplVarsBlocks[] = array(
+                'content' => $this->getBlockCode($aBlock)
+            ); 
+        }
+
+    	return BxDolStudioTemplate::getInstance()->parseHtmlByName('page_blocks_line.html', array(
+    	    'count' => count($aTmplVarsBlocks),
+    		'bx_repeat:blocks' => $aTmplVarsBlocks
+    	));
+    }
+    public function getBlockCode($aBlock)
+    {
+    	return BxDolStudioTemplate::getInstance()->parseHtmlByName('page_block.html', array(
+    		'caption' => $this->getBlockCaption($aBlock),
+    		'panel_top' => $this->getBlockPanelTop($aBlock),
+    		'items' => !empty($aBlock['items']) ? $aBlock['items'] : '',
+    		'panel_bottom' => $this->getBlockPanelBottom($aBlock)
+    	));
+    }
+
+    public function getBlockCaption($aBlock)
+    {
+        if(empty($aBlock) || !is_array($aBlock) || (empty($aBlock['caption']) && empty($aBlock['actions'])))
+            return '';
+
+        $aTmplActions = array();
+        if(!empty($aBlock['actions']) && is_array($aBlock['actions']))
+            foreach($aBlock['actions'] as $aAction) {
+                $sCaption = is_array($aAction['caption']) ? call_user_func_array('_t', $aAction['caption']) : _t($aAction['caption']);
+
+                $bOnClick = !empty($aAction['onclick']);
+                $aOnClick = $bOnClick ? array('onclick' => $aAction['onclick']) : array();
+
+                $aTmplActions[] = array(
+                    'name' => $aAction['name'],
+                    'url' => $aAction['url'],
+                    'title' => $sCaption,
+                    'bx_if:show_onclick' => array(
+                        'condition' => $bOnClick,
+                        'content' => $aOnClick
+                    ),
+                    'caption' => $sCaption
+                );
+            }
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('block_caption.html', array(
+            'caption' => is_array($aBlock['caption']) ? call_user_func_array('_t', $aBlock['caption']) : _t($aBlock['caption']),
+            'bx_if:show_actions' => array(
+                'condition' => !empty($aTmplActions),
+                'content' => array(
+                    'bx_repeat:actions' => $aTmplActions
+                )
+            ),
+        ));
+    }
+
+    public function getBlockPanelTop($aBlock)
+    {
+        if(empty($aBlock) || !is_array($aBlock) || empty($aBlock['panel_top']))
+            return '';
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('block_panel_top.html', array(
+            'content' => $aBlock['panel_top']
+        ));
+    }
+
+    public function getBlockPanelBottom($aBlock)
+    {
+        if(empty($aBlock) || !is_array($aBlock) || empty($aBlock['panel_bottom']))
+            return '';
+
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('block_panel_bottom.html', array(
+            'content' => $aBlock['panel_bottom']
+        ));
+    }
+
+    protected function getJsResult($sMessage, $bTranslate = true, $bRedirect = false, $sRedirect = '', $sOnResult = '')
+    {
+        return $this->getJsResultBy(array(
+            'message' => $sMessage,
+            'translate' => $bTranslate,
+            'redirect' => $bRedirect === true && !empty($sRedirect) ? $sRedirect : $bRedirect,
+            'eval' => $sOnResult
+        ));
+    }
+
+    protected function getJsResultBy($aParams)
+    {
+        $aResult = array();
+
+        if(!empty($aParams['message'])) {
+            $aResult['message'] = $aParams['message'];
+
+            if(!isset($aParams['translate']) || !empty($aParams['translate'])) {
+                $aTrtParams = array($aResult['message']);
+                if(!empty($aParams['translate']) && is_array($aParams['translate']))
+                    $aTrtParams = array_merge($aTrtParams, $aParams['translate']);
+
+                $aResult['message'] = call_user_func_array ('_t', $aTrtParams);
+            }
+        }
+
+        if(isset($aParams['redirect']) && $aParams['redirect'] !== false)
+            $aResult['redirect'] = is_string($aParams['redirect']) ? $aParams['redirect'] : BX_DOL_URL_STUDIO;
+
+        if(!empty($aParams['eval']))
+            $aResult['eval'] = $aParams['eval'];
+
+        $sResult = "window.parent.processJsonData(" . json_encode($aResult) . ");";
+        if(isset($aParams['on_page_load']) && $aParams['on_page_load'] === true)
+            $sResult = "$(document).ready(function() {" . $sResult . "});";
+
+        return BxDolStudioTemplate::getInstance()->_wrapInTagJsCode($sResult);
+    }
+}
+
+/** @} */

+ 2 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/comment_quote.html

@@ -0,0 +1,2 @@
+<blockquote>__content__</blockquote>
+<p></p>

+ 430 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/css/cmts.css

@@ -0,0 +1,430 @@
+    /*--- General Styles ---*/
+    div.cmts {}
+
+    ul.cmts {
+    	position: relative;
+        margin: 0px;
+        padding: 0px;
+        list-style: none;
+    }
+    ul.cmts-margin,
+    div.cmt-reply-margin {
+    	margin-top: 20px;
+    	margin-top: 1.2rem;
+
+		padding-left: 60px;
+    	padding-left: 3.75rem;
+    }
+    li.cmt {
+    	position: relative;
+    }
+    li.cmt-no .bx-msg-box-container {
+        margin-top: 0px;
+        margin-bottom: 0px;
+    }
+    li.cmt-err {
+        color: red;
+    }
+    li.cmt-hidden {
+       display: none;
+    }
+    li.cmt-replacement {
+    	position: relative;
+		height: 30px;
+		height: 1.875rem;
+		background-color: #ededed;
+		text-indent: 10px;
+    	text-indent: 0.5rem;
+		line-height: 30px;
+    	line-height: 1.875rem;
+    	overflow: hidden;
+    }
+    li.cmt-blink div.cmt-blink-plate {
+		display: block;
+    }
+    div.cmt-cnt {
+		position: relative;
+    }
+
+	/*--- More ---*/
+    li.cmt-more,
+	li.cmt-more-root {
+		text-transform:uppercase;
+	}
+
+	div.cmt-more-cont {
+		font-weight: bold;
+	}
+
+	
+    li.cmt-more-root div.cmt-more-cont {
+    	position: relative;
+    }
+    li.cmt-more-root .bx-btn {
+    	position: relative;
+		float: none;
+    	display: inline-block;
+    	text-transform: uppercase;
+    }    
+
+    /*--- Comment ---*/
+	div.cmt-avatar {
+		position: absolute;
+		top: 0px;
+		left: 0px;
+    	text-align: center;
+    }
+    div.cmt-avatar .sys-icon {
+    	font-size: 60px;
+		font-size: 3.75rem;
+    }
+    div.cmt-info {
+    	position: realtive;
+    	overflow: hidden;
+	}
+	div.cmt-blink-plate {
+		position: absolute;
+		display: none;
+		top: -10px;
+		left: -10px;
+		width: 100%;
+		height: 100%;
+		padding: 10px;
+    	background-color: rgb(255, 244, 213);
+    	background-color: rgba(255, 234, 193, 0.5);
+    }
+	div.cmt-head {
+		text-align: left;
+    	text-overflow: ellipsis;
+		white-space: nowrap;
+		overflow: hidden;
+	}
+    span.cmt-reply-to a {
+		text-decoration: none;
+    }
+    span.cmt-posted-ago {
+        text-transform: lovercase;
+    }
+	span.cmt-points {
+        text-transform: lovercase;
+    }
+
+    /*--- unit meta ---*/
+    .cmt-meta {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        text-align: left;
+    }
+    .cmt-meta ul.bx-menu-custom.bx-menu-hor > li > span {
+            margin-right: 0;
+    }
+    .cmt-meta ul.bx-menu-custom li:first-child .cmt-meta-div {
+        display: none;
+    }
+    .cmt-meta ul.bx-menu-custom li .cmt-meta-item,
+    .cmt-meta ul.bx-menu-custom li .cmt-meta-div,
+    .cmt-meta ul.bx-menu-custom li .bx-btn {
+        position: relative;
+        float: none;
+        display: inline-block;
+        vertical-align: middle;
+    }
+    .cmt-meta ul.bx-menu-custom li .cmt-meta-item .cmt-meta-ago,
+    .cmt-meta ul.bx-menu-custom li .cmt-meta-item .cmt-meta-membership {
+        font-size: 12px;
+        font-size: .75rem;
+    }
+
+    /*--- Comment Body ---*/
+    div.cmt-cont {
+        position: relative;
+    	text-align: left;
+    }
+    div.cmt-cont .bullet {
+        padding: 0px 5px;
+        padding: 0px 0.3rem;
+    }
+    div.cmt-cont .cmt-body > p:first-child {
+        margin-top: 0;
+        margin-bottom: 0;
+    }
+    div.cmt-cont .cmt-body img {
+        max-width: 100%;
+    	height: auto;
+    }
+    div.cmt-cont .cmt-post-reply .bx-form-element-wrapper {
+        margin-top: 10px;
+    	margin-top: 0.5rem;
+    }
+    div.cmt-cont .cmt-post-reply .bx-form-element-wrapper:first-child {
+        margin-top: 0px;
+    }
+    span.cmt-body-see-more a {
+        font-weight: bold;
+    }
+    div.cmt-reactions .bx-vote-counter-wrapper {
+        line-height: 2.0rem;
+        font-size: 1.2rem;
+    }
+    div.cmt-attached-preview {
+        overflow:hidden;
+    }
+    div.cmt-attached-preview,
+    div.cmt-attached-preview img {
+    	width: 100px;
+    	width: 6.25rem;
+    	height: 100px;
+    	height: 6.25rem;
+    }
+    div.cmt-attached-preview {
+    	position: relative;
+    	float: left;
+	}
+    div.cmt-attached-preview img {
+        background-position: center center;
+        background-repeat: no-repeat;
+        background-size: contain;
+    }
+
+    /*--- Comment Actions ---*/
+    div.cmt-actions {
+        position: relative;
+        text-align: left;
+    }
+    div.cmt-meta-ago,
+    div.cmt-comment-manage-link, 
+    div.cmt-actions ul.bx-menu-hor,
+    div.cmt-points-wrp {
+        position: relative;
+        float: left;
+    }
+    div.cmt-actions li .bx-vote {
+       margin-right: 1.25rem;
+    }
+    div.cmt-comment-manage .bx-menu-ver li {
+		white-space: nowrap;
+    }
+
+    /*--- Add Comment/Reply Styles ---*/
+    div.cmt-reply {
+		position: relative;
+    }
+	div.cmt-post-reply {
+        position: relative;
+    }
+
+    /*--- Add Comment/Reply Form Styles ---*/
+    form.cmt-post-reply .bx-form-input-textarea {
+		height: 64px;
+    	height: 4rem;
+    }
+    form.cmt-post-reply div#bx-form-element-cmt_image,
+    form.cmt-post-reply .bx-form-input-files-uploaders {
+		margin-top: 0px;
+    }
+
+    a.cmts-uploader-button {
+		position: absolute;
+    	z-index: 1;
+    	right: 0px;
+
+    	text-decoration: none;
+    }
+    div.cmts-uploader-file {
+		position: relative;
+    	float: left;
+    }
+    div.cmts-uploader-file-preview {
+		overflow: hidden;
+    }
+    div.cmts-uploader-file-preview,
+    div.cmts-uploader-file-preview img {
+		width: 100px;
+    	width: 6.25rem;
+    	height: 100px;
+    	height: 6.25rem;
+    }
+    div.cmts-uploader-file-preview img {
+		background-position: center center;
+		background-repeat: no-repeat;
+    } 
+    
+    /*--- Minified Post Form ---*/
+    div.cmt-reply.cmt-reply-min div.cmt-body {
+        display: none;
+    }
+    div.cmt-reply div.cmt-body-min {
+        position: relative;
+        display: none;
+    }
+    div.cmt-reply.cmt-reply-min div.cmt-body-min {
+        display: block;
+    }
+    div.cmt-reply div.cmt-body-min .cmt-bm-user {
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+    div.cmt-reply div.cmt-body-min .cmt-bm-content {
+        display: -webkit-flex;
+        display: flex;
+        -webkit-align-items: center;
+        align-items: center;
+        -webkit-justify-content: stretch;
+        justify-content: stretch;
+    }
+    div.cmt-reply div.cmt-body-min .cmt-bm-content .bx-form-element-wrapper {
+        position: relative;
+        width: 100%;
+    }
+
+    /*--- Live Update Alert Styles ---*/
+    div.cmt-lu-button {
+        position: relative;
+        text-align: center;
+    }
+    @supports(position:sticky) {
+        div.cmt-lu-button {
+            position: sticky;
+            top: 1rem;
+            z-index: 1;
+        }
+    }
+    @supports(position:-webkit-sticky) {
+        div.cmt-lu-button {
+            position: -webkit-sticky;
+            top: 1rem;
+            z-index: 1;
+        }
+    }
+    div.cmt-lu-button .bx-btn {
+        display: inline-block;
+        float: none;
+    }
+
+    /*--- Live Update Notifications Styles ---*/
+    .cmt-lu-notif {
+        position: relative;
+        display: block;
+    }
+    div.cmt-lu-notif-cnt {
+        position: relative;
+    }
+    div.cmt-lu-notif-avatar {
+        position: absolute;
+        top: 0px;
+        left: 0px;
+        text-align: center;
+    }
+    div.cmt-lu-notif-info {
+        position: realtive;
+        display: -webkit-flex;
+        display: flex;
+        -webkit-align-items: center;
+        align-items: center;
+    }
+    div.cmt-lu-notifi-username {
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+    }
+        
+/*--- Comment Element Styles ---*/
+div.cmt div.cmt-element-holder,
+div.cmt div.cmt-counter-holder {
+	position: relative;
+	float: left;
+}
+
+.cmt .cmt-element-holder,
+.cmt .cmt-counter-holder {
+	line-height: 20px;
+	line-height: 1.25rem;
+}
+
+.cmt-button .cmt-element-holder,
+.cmt-button .cmt-counter-holder {
+	line-height: 36px;
+	line-height: 2.2rem;
+}
+
+.cmt-button-small .cmt-element-holder,
+.cmt-button-small .cmt-counter-holder {
+	line-height: 24px;
+	line-height: 1.6rem;
+}
+
+.cmt .cmt-element-holder .cmt-do-comment.cmt-disabled,
+.cmt .cmt-element-holder .cmt-do-comment.cmt-disabled .sys-icon {
+	color: #ddd;
+}
+
+.cmt-button .cmt-counter-holder,
+.cmt-button-small .cmt-counter-holder {
+	font-size: 1.5rem;
+}
+
+.cmt-link .cmt-counter-holder.bx-btn-border {
+	border-width: 0px;
+}
+
+
+/*----------------------*/
+/*--- Reviews Styles ---*/
+/*----------------------*/
+
+/*--- Mood Element Styles ---*/
+div.cmt-mood {
+    position: relative;
+    display: inline-block;
+}
+div.cmt-mood-underlay {}
+div.cmt-mood-slider {
+    position: absolute;
+    z-index: 1;
+    top: 0px;
+    left: 0px;
+    white-space: nowrap;
+    overflow: hidden;
+}
+div.cmt-mood a.cmt-mood-button,
+div.cmt-mood i.cmt-mood-star {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+    height: 16px;
+    width: 16px;
+    margin: 0;
+    padding: 0;
+    text-align: center;
+}
+div.cmt-mood-underlay i.cmt-mood-star {
+    color: rgba(255, 204, 102, 1);
+}
+div.cmt-mood-slider i.cmt-mood-star,
+div.cmt-mood-buttons a:hover i.cmt-mood-star {
+    color: rgba(255, 100, 0, 1);
+}
+
+div.cmt-mood-buttons {
+    position: absolute;
+    z-index: 2;
+    top: 0px;
+    left: 0px;
+}
+div.cmt-mood-buttons a:hover {
+	text-decoration: none;
+}
+div.cmt-mood-buttons i.cmt-mood-star {
+    color: transparent;
+}
+
+/*--- Rating Block Styles ---*/
+.cmts-box-rating div.cmt-mood i.cmt-mood-star,
+.cmt-rating-element div.cmt-mood i.cmt-mood-star,
+.cmt-rating-element div.cmt-mood a.cmt-mood-button {
+    height: 24px;
+    width: 24px;
+    font-size: 20px;
+}

+ 703 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/css/default.less

@@ -0,0 +1,703 @@
+@import '_mixins.less';
+
+.bx-def-page-width {
+    width:@bx-page-width;
+}
+
+.bx-def-box-sizing {
+    .box-sizing(border-box);
+}
+
+/* margins */
+.bx-def-centered {
+	margin: 0px auto;
+}
+
+.bx-def-margin { 
+    .margin(@bx-margin);
+}
+.bx-def-margin-neg { 
+    .margin(-@bx-margin);
+}
+.bx-def-margin-lefttopright {
+    .margin-left(@bx-margin);
+    .margin-top(@bx-margin);
+    .margin-right(@bx-margin);
+}
+.bx-def-margin-rightbottomleft {
+    .margin-right(@bx-margin);
+    .margin-bottom(@bx-margin);
+    .margin-left(@bx-margin);
+}
+.bx-def-margin-leftright {
+    .margin-left(@bx-margin);
+    .margin-right(@bx-margin);
+}
+.bx-def-margin-leftright-neg {
+    .margin-left(-@bx-margin);
+    .margin-right(-@bx-margin);
+}
+.bx-def-margin-topbottom {
+    .margin-top(@bx-margin);
+    .margin-bottom(@bx-margin);
+}
+.bx-def-margin-topbottom-neg {
+    .margin-top(-@bx-margin);
+    .margin-bottom(-@bx-margin);
+}
+.bx-def-margin-top {
+    .margin-top(@bx-margin);
+}
+.bx-def-margin-top-auto {
+    .margin-top(@bx-margin);
+    &:first-child {
+        .margin-top(0px);
+    }
+}
+.bx-def-margin-right {
+    .margin-right(@bx-margin);
+}
+.bx-def-margin-bottom {
+    .margin-bottom(@bx-margin);
+}
+.bx-def-margin-bottom-neg {
+    .margin-bottom(-@bx-margin);
+}
+.bx-def-margin-left {
+    .margin-left(@bx-margin);
+}
+.bx-def-margin-left-auto {
+    .margin-left(@bx-margin);
+    &:first-child {
+        .margin-left(0);
+    }
+}
+.bx-def-margin-right-auto {
+    .margin-right(@bx-margin);
+    &:last-child {
+        .margin-right(0);
+    }
+}
+
+.bx-def-margin-sec {
+    .margin(@bx-margin-sec);
+}
+.bx-def-margin-sec-neg {
+    .margin(-@bx-margin-sec);
+}
+.bx-def-margin-sec-lefttopright {
+	.margin-left(@bx-margin-sec);
+    .margin-top(@bx-margin-sec);
+    .margin-right(@bx-margin-sec);
+}
+.bx-def-margin-sec-lefttopright-neg {
+	.margin-left(-@bx-margin-sec);
+    .margin-top(-@bx-margin-sec);
+    .margin-right(-@bx-margin-sec);
+}
+.bx-def-margin-sec-bottom-neg {
+    .margin-bottom(-@bx-margin-sec);
+}
+.bx-def-margin-sec-rightbottomleft {
+    .margin-right(@bx-margin-sec);
+    .margin-bottom(@bx-margin-sec);
+    .margin-left(@bx-margin-sec);
+}
+.bx-def-margin-sec-leftright {
+    .margin-left(@bx-margin-sec);
+    .margin-right(@bx-margin-sec);
+}
+.bx-def-margin-sec-leftright-neg {
+    .margin-left(-@bx-margin-sec);
+    .margin-right(-@bx-margin-sec);
+}
+.bx-def-margin-sec-topbottom {
+    .margin-top(@bx-margin-sec);
+    .margin-bottom(@bx-margin-sec);
+}
+.bx-def-margin-sec-top {
+    .margin-top(@bx-margin-sec);
+}
+.bx-def-margin-sec-top-auto {
+    .margin-top(@bx-margin-sec);
+    &:first-child {
+        .margin-top(0);
+    }
+}
+.bx-def-margin-sec-right {
+    .margin-right(@bx-margin-sec);
+}
+.bx-def-margin-sec-bottom {
+    .margin-bottom(@bx-margin-sec);
+}
+.bx-def-margin-sec-left {
+    .margin-left(@bx-margin-sec);
+}
+.bx-def-margin-sec-left-auto {
+    .margin-left(@bx-margin-sec);
+    &:first-child {
+        .margin-left(0);
+    }
+}
+.bx-def-margin-sec-right-auto {
+    .margin-left(@bx-margin-sec);
+    &:last-child {
+        .margin-left(0);
+    }
+}
+
+.bx-def-margin-thd {
+    .margin(@bx-margin-thd);
+}
+.bx-def-margin-thd-neg {
+    .margin(-@bx-margin-thd);
+}
+.bx-def-margin-thd-rightbottomleft {
+    .margin-right(@bx-margin-thd);
+    .margin-bottom(@bx-margin-thd);
+    .margin-left(@bx-margin-thd);
+}
+.bx-def-margin-thd-leftright {
+    .margin-left(@bx-margin-thd);
+    .margin-right(@bx-margin-thd);
+}
+.bx-def-margin-thd-leftright-neg {
+	.margin-left(-@bx-margin-thd);
+    .margin-right(-@bx-margin-thd);
+}
+.bx-def-margin-thd-topbottom {
+    .margin-top(@bx-margin-thd);
+    .margin-bottom(@bx-margin-thd);
+}
+.bx-def-margin-thd-top {
+    .margin-top(@bx-margin-thd);
+}
+.bx-def-margin-thd-top-auto {
+    .margin-top(@bx-margin-thd);
+    &:first-child {
+        .margin-top(0);
+    }
+}
+.bx-def-margin-thd-right {
+    .margin-right(@bx-margin-thd);
+}
+.bx-def-margin-thd-bottom {
+    .margin-bottom(@bx-margin-thd);
+}
+.bx-def-margin-thd-left {
+    .margin-left(@bx-margin-thd);
+}
+.bx-def-margin-thd-left-auto {
+    .margin-left(@bx-margin-thd);
+    &:first-child {
+        .margin-left(0);
+    }
+}
+
+/* paddings */
+.bx-def-padding {
+    .padding(@bx-padding);
+}
+.bx-def-padding-lefttopright {
+    .padding-left(@bx-padding);
+    .padding-top(@bx-padding);
+    .padding-right(@bx-padding);
+}
+.bx-def-padding-rightbottomleft {
+    .padding-right(@bx-padding);
+    .padding-bottom(@bx-padding);
+    .padding-left(@bx-padding);
+}
+.bx-def-padding-leftright {
+    .padding-left(@bx-padding);
+    .padding-right(@bx-padding);
+}
+.bx-def-padding-topbottom {
+    .padding-top(@bx-padding);
+    .padding-bottom(@bx-padding);
+}
+.bx-def-padding-top {
+    .padding-top(@bx-padding);
+}
+.bx-def-padding-right {
+    .padding-right(@bx-padding);
+}
+.bx-def-padding-bottom {
+    .padding-bottom(@bx-padding);
+}
+.bx-def-padding-left {
+    .padding-left(@bx-padding);
+}
+
+.bx-def-padding-sec {
+    .padding(@bx-padding-sec);
+}
+.bx-def-padding-sec-rightbottomleft {
+    .padding-right(@bx-padding-sec);
+    .padding-bottom(@bx-padding-sec);
+    .padding-left(@bx-padding-sec);
+}
+.bx-def-padding-sec-topbottom {
+    .padding-top(@bx-padding-sec);
+    .padding-bottom(@bx-padding-sec);
+}
+.bx-def-padding-sec-leftright {
+    .padding-left(@bx-padding-sec);
+    .padding-right(@bx-padding-sec);
+}
+.bx-def-padding-sec-top {
+    .padding-top(@bx-padding-sec);
+}
+.bx-def-padding-sec-right {
+    .padding-right(@bx-padding-sec);
+}
+.bx-def-padding-sec-bottom {
+    .padding-bottom(@bx-padding-sec);
+}
+.bx-def-padding-sec-left {
+    .padding-left(@bx-padding-sec);
+}
+
+.bx-def-padding-thd {
+    .padding(@bx-padding-thd);
+}
+.bx-def-padding-thd-rightbottomleft {
+	.padding-right(@bx-padding-thd);
+	.padding-bottom(@bx-padding-thd);
+    .padding-left(@bx-padding-thd);
+}
+.bx-def-padding-thd-topbottom {
+    .padding-top(@bx-padding-thd);
+    .padding-bottom(@bx-padding-thd);
+}
+.bx-def-padding-thd-leftright {
+    .padding-left(@bx-padding-thd);
+    .padding-right(@bx-padding-thd);
+}
+.bx-def-padding-thd-top {
+    .padding-top(@bx-padding-thd);
+}
+.bx-def-padding-thd-right {
+    .padding-right(@bx-padding-thd);
+}
+.bx-def-padding-thd-bottom {
+    .padding-bottom(@bx-padding-thd);
+}
+.bx-def-padding-thd-left {
+    .padding-left(@bx-padding-thd);
+}
+
+/* colors */
+.bx-def-color-bg-page {
+    .background-color (@bx-color-page);
+}
+.bx-def-color-bg-block {
+    .background-color (@bx-color-block);
+}
+.bx-def-color-bg-box {
+    .background-color (@bx-color-box);
+}
+.bx-def-color-bg-box-active,
+.bx-def-color-bg-box-hover:hover {
+    .background-color (@bx-color-box-hover);
+}
+.bx-def-color-bg-sec {
+    .background-color (@bx-color-sec);
+}
+
+.bx-def-color-bg-hl,
+.bx-def-color-bg-hl-even:nth-child(even),
+.bx-def-color-bg-hl-hover:hover {
+    .background-color (@bx-color-hl);
+}
+
+.bx-def-color-bg-active {
+    .background-color (@bx-color-active);
+}
+
+.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 (@bx-color-disabled);
+}
+
+/* hr */
+.bx-def-hr,
+.bx-def-hr-auto {
+    margin-left:0;
+    margin-right:0;
+    padding:0;
+    border:0px;
+    border-top:@bx-border-width @bx-border-type @bx-border-color-hr;
+}
+.bx-def-hr-auto:first-child {
+	display: none;
+}
+
+/* border for common layout elements likedesign boxes, pads, menu items, etc */
+.bx-def-border {
+    border:@bx-border-width @bx-border-type @bx-border-color;
+}
+.bx-def-border-rightbottomleft {
+    border-right:@bx-border-width @bx-border-type @bx-border-color;
+    border-bottom:@bx-border-width @bx-border-type @bx-border-color;
+    border-left:@bx-border-width @bx-border-type @bx-border-color;
+}
+.bx-def-border-top {
+    border-top:@bx-border-width @bx-border-type @bx-border-color;
+}
+.bx-def-border-top-auto {
+    border-top:@bx-border-width @bx-border-type @bx-border-color;
+    &:first-child {
+        border-top:none;
+    }
+}
+.bx-def-border-right {
+    border-right:@bx-border-width @bx-border-type @bx-border-color;
+}
+.bx-def-border-bottom {
+    border-bottom:@bx-border-width @bx-border-type @bx-border-color;
+}
+.bx-def-border-left {
+    border-left:@bx-border-width @bx-border-type @bx-border-color;
+}
+.bx-def-border-right {
+    border-right:@bx-border-width @bx-border-type @bx-border-color;
+}
+
+/* shadow */
+.bx-def-shadow {
+    .drop-shadow (0px, 1px, 3px, 0px, rgba(51, 51, 51, 0.1));
+}
+
+/* round corders */
+.bx-def-round-corners {
+    .radius(@bx-round-corners-radius);
+}
+
+/* blur */
+.bx-def-blur {
+    .blur(2px);
+}
+
+/* fonts */
+
+/* NOTE: bx-def-font, h1, h2, h3 styles were copied to editor.css */
+
+.bx-def-font {
+    font-family:@bx-font-family;
+    font-weight:400; /* 26.13.13: prev val 300 */
+    .font-size(@bx-font-size-default);
+    color:@bx-font-color-default;
+}
+
+.bx-def-font-inputs {
+    font-family:@bx-font-family;
+    font-weight:400; /* 27.13.13: prev val 300 */
+    .font-size(@bx-font-size-default);
+    /* TODO: Remove 'color' instruction later. It affects the font color in Buttons which is usually differs from text color in inputs. */
+    /*color:@bx-font-color-default; */ 
+}
+
+.bx-def-font-color,
+.bx-def-font-color:hover {
+	color:@bx-font-color-default;
+}
+
+.bx-def-font-grayed {
+    color:@bx-font-color-grayed;
+}
+
+.bx-def-font-contrasted:after,
+.bx-def-font-contrasted:before,
+.bx-def-font-contrasted {
+    color:@bx-font-color-contrasted;
+}
+
+.bx-def-font-small {
+    .font-size(@bx-font-size-small);
+}
+
+.bx-def-font-middle {
+    .font-size(@bx-font-size-middle);
+}
+
+.bx-def-font-large {
+    .font-size(@bx-font-size-large);
+}
+
+.bx-def-font-light {
+	font-weight: 300;
+}
+
+.bx-def-font-normal {
+	font-weight: 400;
+}
+
+.bx-def-font-semibold {
+	font-weight: 600;
+}
+
+.bx-def-font-bold {
+	font-weight: 700;
+}
+
+.bx-def-font-extrabold {
+	font-weight: 800;
+}
+
+.bx-def-font-align-center {
+	text-align: center;
+}
+
+h1,
+.bx-def-font-h1 {
+    .font-size(@bx-font-size-h1);
+    font-weight:700;
+}
+
+h2,
+.bx-def-font-h2 {
+    .font-size(@bx-font-size-h2);
+    font-weight:700;
+}
+
+h3,
+.bx-def-font-h3 {
+    .font-size(@bx-font-size-h3);
+    font-weight:500;
+}
+
+/* image/thumbnail/gallery default styles */
+
+
+.bx-def-icon,
+.bx-def-thumb,
+.bx-def-ava,
+.bx-def-ava-big {
+    .radius(200px);
+    overflow:hidden;
+}
+
+.bx-def-ava-big-size {
+    .width(@bx-size-avatar-big);
+    .height(@bx-size-avatar-big);
+    .line-height(@bx-size-avatar-big);
+}
+.bx-def-ava-big-size-max-height {
+    .max-height(@bx-size-avatar-big);
+}
+.bx-def-ava-big-size-min-height {
+    .min-height(@bx-size-avatar-big);
+}
+.bx-def-ava-big-size-left-indent {
+    .margin-left(@bx-size-avatar-big + @bx-margin);
+}
+
+
+.bx-def-ava-size {
+    .width(@bx-size-avatar);
+    .height(@bx-size-avatar);
+    .line-height(@bx-size-avatar);
+}
+.bx-def-ava-size-max-height {
+    .max-height(@bx-size-avatar);
+}
+.bx-def-ava-size-min-height {
+    .min-height(@bx-size-avatar);
+}
+.bx-def-ava-size-left-indent {
+    .margin-left(@bx-size-avatar + @bx-margin);
+}
+
+
+.bx-def-thumb-size {
+    .width(@bx-size-thumb);
+    .height(@bx-size-thumb);
+    .line-height(@bx-size-thumb);
+}
+.bx-def-thumb-size-max-height {
+    .max-height(@bx-size-thumb);
+}
+.bx-def-thumb-size-min-height {
+    .min-height(@bx-size-thumb);
+}
+.bx-def-thumb-size-left-indent {
+	.margin-left(@bx-size-thumb + @bx-margin);
+}
+
+.bx-def-icon-size {
+    .width(@bx-size-icon);
+    .height(@bx-size-icon);
+}
+.bx-def-icon-size-max-height {
+    .max-height(@bx-size-icon);
+}
+.bx-def-icon-size-min-height {
+    .min-height(@bx-size-icon);
+}
+.bx-def-icon-size-left-indent {
+    .margin-left(@bx-size-icon + @bx-margin);
+}
+
+
+.bx-def-unit {
+    position:relative;
+    white-space:nowrap;
+}
+.bx-def-unit .bx-def-unit-info,
+.bx-def-unit .bx-def-unit-img,
+.bx-def-unit img {
+    display:inline;
+    display:inline-block;
+    vertical-align:middle;
+    .line-height(19px);
+}
+.bx-def-unit.absolute .bx-def-unit-img {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+}
+.bx-def-unit.absolute .bx-def-unit-info {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.bx-def-unit.absolute .bx-def-unit-info-cnt {
+    width: 100%;
+}
+.bx-def-unit-info {
+    .box-sizing(border-box);
+
+    text-overflow:ellipsis;
+    overflow:hidden;
+}
+.bx-def-unit.absolute .bx-def-unit-line {
+    text-overflow:ellipsis;
+    overflow:hidden;
+}
+.bx-def-unit.absolute .bx-def-unit-line .bx-def-ui-div:first-child {
+    display: none;
+}
+
+.bx-def-unit-alert,
+.bx-def-unit-alert-small,
+.bx-def-unit-alert-middle {
+    display:block;
+    position:absolute;
+    top: 0;
+    right: -2px;
+    text-align:center;
+    color:white;
+}
+.bx-def-unit-alert {
+	.min-width(@bx-font-size-middle);
+    .height(@bx-font-size-middle);
+    .line-height(@bx-font-size-middle);
+    .radius(@bx-font-size-middle);
+    .font-size(@bx-font-size-middle);
+    .padding(3px);
+}
+.bx-def-unit-alert-small {
+	.min-width(@bx-font-size-small + 1);
+    .height(@bx-font-size-small + 1);
+    .line-height(@bx-font-size-small + 1);
+    .radius(@bx-font-size-small);
+    .font-size(@bx-font-size-small);
+     .padding(1px);
+}
+.bx-def-unit-alert-middle {
+    .min-width(@bx-font-size-middle - 2);
+    .height(@bx-font-size-middle - 2);
+    .line-height(@bx-font-size-middle - 2);
+    .radius(@bx-font-size-middle - 2);
+    .font-size(@bx-font-size-middle - 2);
+    .padding(3px);
+}
+
+
+.bx-def-gal-img-column {
+    .column-width-raw(100%);
+}
+
+.bx-def-gal-img-size {
+    .width(@bx-size-gallery-img-width);
+    .height(@bx-size-gallery-img-height);
+}
+
+.bx-def-gal-div-size {
+    width:100%;
+    .height(@bx-size-gallery-img-height);
+}
+.bx-def-gal-unit-column {
+    .column-width-raw(100%);
+}
+.bx-def-gal-unit-size {
+    display:inline-block;
+    width:100%; 
+    height:auto;
+    vertical-align:top;
+    overflow:hidden;
+    .box-sizing();
+}
+
+
+/* label */
+
+.bx-def-label {
+    .box-sizing(border-box);
+
+    min-width: 1.5rem;
+    .padding-left(@bx-padding-sec);
+    .padding-right(@bx-padding-sec);
+
+    .radius(@bx-round-corners-radius-label);
+
+    background-color: @bx-color-label;
+    
+    line-height: 1.5rem;
+    font-size: @bx-font-size-small;
+}
+.bx-def-label,
+.bx-def-label a,
+.bx-def-label .sys-icon {
+    color: @bx-font-color-grayed;
+}
+
+.bx-def-label.menu {
+    .radius(@bx-round-corners-radius-label-menu);
+
+    background-color: @bx-color-label-menu;
+}
+
+.bx-def-label.menu,
+.bx-def-label.menu a,
+.bx-def-label.menu .sys-icon {
+    color: @bx-font-color-contrasted;
+}
+
+
+/* z-index */
+
+.bx-def-z-index-front {
+    z-index:100;
+}
+
+.bx-def-z-index-nav {
+    z-index:400;
+}
+
+.bx-def-z-index-overlay {
+    z-index:700;
+}
+
+.bx-def-z-index-modal {
+    z-index:1000;
+}

+ 486 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/css/general.css

@@ -0,0 +1,486 @@
+
+.bx-page-image-container img,
+.bx-page-html-container img {
+    max-width:100%;
+    height:auto;
+}
+.bx-page-html-container {
+    overflow:hidden;
+}
+
+/* --------------- toolbar */
+
+.bx-toolbar-anim {
+    -webkit-transition: all .25s ease-in-out;
+    -moz-transition: all .25s ease-in-out;
+    -o-transition: all .25s ease-in-out;
+    transition: all .25s ease-in-out
+}
+
+.bx-toolbar-reset {
+    -webkit-transform: translateY(0);
+    -ms-transform: translateY(0);
+    transform: translateY(0)
+}
+
+.bx-toolbar-up {
+    -webkit-transform: translateY(-100%);
+    -ms-transform: translateY(-100%);
+    transform: translateY(-100%)
+}
+
+#bx-toolbar {
+    position:fixed;
+    width:100%;
+    height:48px;
+    height:3rem;
+}
+
+#bx-toolbar-content {
+    position: relative;
+}
+
+#bx-menu-toolbar-1-container,
+#bx-menu-toolbar-2-container {
+    position:absolute;
+    top:0px;
+}
+
+#bx-menu-toolbar-1-container {
+    left:0px;
+    text-align:left;
+}
+
+#bx-menu-toolbar-2-container {
+    right:0px;
+    text-align:right;
+}
+
+#bx-logo-container {
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    box-sizing: border-box;
+
+    height: 48px;
+    height: 3rem;
+           
+    margin:0 48px;
+    margin:0 6rem;
+    
+    vertical-align: middle;
+    text-align: center;
+}
+
+#bx-logo-container a span {
+    display:inline-block;
+
+    max-width:100%;
+
+    line-height: 48px;
+    line-height: 3rem;
+
+    white-space: nowrap;
+    overflow:hidden;
+    text-overflow:ellipsis;
+}
+
+#bx-logo-container a {
+    text-decoration:none;
+    font-size:32px;
+    font-size:2rem;
+}  
+
+#bx-logo {
+    vertical-align:middle;
+}
+
+/* --------------- Submenu */
+.bx-menu-main-bar-gap {
+    position: relative;
+}
+.bx-media-tablet .bx-menu-main-bar-gap.bx-def-page-width,
+.bx-media-phone .bx-menu-main-bar-gap.bx-def-page-width {
+    width: 100%;
+}
+.bx-media-phone .bx-menu-main-bar-gap {
+    padding-left: 0;
+    padding-right: 0;
+}
+.bx-menu-main-bar-wrapper {
+    position: relative;
+
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+.bx-menu-main-bar-wrapper:before,
+.bx-menu-main-bar-wrapper:after {
+    display: block;
+    content: "";
+    position: absolute;
+    top: 0;
+    width: 1.5rem;
+    height: 100%;
+    z-index: 1;
+}
+.bx-menu-main-bar-wrapper:before {
+    left: 0px;
+}
+.bx-menu-main-bar-wrapper:after {
+    right: 0px;
+}
+.bx-media-phone .bx-menu-main-bar-wrapper {
+    border-left-width: 0;
+    border-right-width: 0;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+.bx-media-phone .bx-menu-main-bar-wrapper:before {
+    border-bottom-left-radius: 0;
+}
+.bx-media-phone .bx-menu-main-bar-wrapper:after {
+    border-bottom-right-radius: 0;
+}
+
+.bx-menu-main-bar {
+    text-align:center;
+    overflow-x:auto;
+    -webkit-overflow-scrolling: touch;
+}
+
+/* --------------- content */
+
+#bx-content-wrapper {
+	position: relative;
+	-webkit-flex-grow: 3; 
+    flex-grow: 3;
+}
+
+/* --------------- footer */
+
+#bx-footer-wrapper {
+    position: relative;
+
+    -webkit-flex-grow: 0;
+    flex-grow: 0;
+
+    width: 100%;
+    bottom: 0;
+}
+#bx-footer-wrapper .bx-def-hr {
+	margin-top: 0px;
+}
+#bx-menu-bottom ul {
+    text-align: center;
+}
+#bx-menu-bottom ul li {
+    line-height: 2rem;
+}
+.bx-menu-switch-language-inline {
+	float: none;
+	clear: both;
+}
+.bx-menu-switch-language-inline .bx-menu-hor {
+	text-align: center;
+}
+.bx-menu-switch-language-inline .bx-menu-hor li {
+	float: none;
+	display: inline-block;
+}
+
+/* --------------- 'Add to Homescreen' popup */
+.ath-container {
+	color: #333;
+}
+
+/* --------------- Account */
+p.bx-account-unit-thumb {
+    display: block;
+    margin: 0px;
+
+    background: #ffffff;
+    background-clip: padding-box;
+    box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2);
+
+    line-height: 46px;
+    line-height: 2.9rem;
+
+    font-size: 32px;
+    font-size: 2.0rem;
+    font-weight: 700;
+    color: #d0d0d0;
+}
+p.bx-account-unit-thumb.bx-def-icon-size {
+    line-height: 26px;
+    line-height: 1.75rem;
+
+    font-size: 19.5px;
+    font-size: 1.3rem;
+}
+p.bx-account-unit-thumb.bx-def-border {
+    border-width: 2px;
+    border-color: #ffffff;
+}
+div.bx-account-unit-online {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: 0.625rem;
+    height: 0.625rem;
+    border: 1px solid #ffffff;
+    border-radius: 0.625rem;
+    background-color: #7ed321;
+}
+
+/* --------------- Mentions */
+
+.bx-mention-row img {
+    width:1rem;
+    height:1rem;
+    vertical-align:middle;
+}
+
+.bx-mention-row span {
+    vertical-align:middle;
+}
+
+
+/* --------------- Profile Stats */
+.bx-profile-stats {
+    overflow: hidden;
+}
+
+.bx-profile-stats .bx-ps-user-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+
+.bx-profile-stats .bx-ps-user-cnt .bx-def-ava-size {
+    width: 3.75rem;
+    height: 3.75rem;
+}
+
+.bx-profile-stats .bx-ps-user-cnt .bx-def-ava-size,
+.bx-profile-stats .bx-ps-user-cnt div.bx-def-unit-img p.bx-base-pofile-unit-thumb.bx-def-ava-size {
+    line-height: 3.5rem;
+    font-size: 2.325rem;
+}
+
+.bx-profile-stats .bx-ps-user-img {
+    -webkit-flex: 0 2 auto; 
+    flex: 0 2 auto;
+}
+
+.bx-profile-stats .bx-ps-user-info {
+    -webkit-flex: 1 1 auto; 
+    flex: 1 1 auto;
+}
+
+.bx-profile-stats .bx-ps-user-info-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+}
+
+
+/* --------------- Create Post Form */
+
+.sys-create-post-form {
+	position: relative;
+}
+
+.sys-create-post-form .bx-loading-ajax {
+	display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    justify-content: center;
+
+	top: 0;
+	width: 100%;
+	height: 100%;
+}
+
+.sys-cpf-header {
+	display: -webkit-flex;
+    display: flex;
+
+	border-bottom-color: #e6f0fa;
+    border-bottom-width: 2px;
+}
+.sys-cpf-title,
+.sys-cpf-submenu {
+	-webkit-flex: 0 1 auto; 
+    -ms-flex:  0 1 auto; 
+    flex:  0 1 auto;
+    line-height: 3.125rem;
+}
+.sys-cpf-title,
+.sys-cpf-submenu-cnt {
+	height: 3.125rem;
+}
+
+.sys-cpf-title {
+    -webkit-flex-grow: 3;
+    flex-grow: 3;
+
+    border-bottom: 2px solid rgba(0,0,0,0.1);
+
+    white-space: nowrap;
+    font-size: 1rem;
+    font-weight: 400;
+}
+.sys-cpf-submenu {
+	overflow-x: auto;
+	overflow-y: hidden;
+
+	-webkit-overflow-scrolling: touch;
+}
+.sys-cpf-submenu::-webkit-scrollbar {
+    width: 0.5rem;
+}
+.sys-cpf-submenu::-webkit-scrollbar-track {
+    -webkit-border-radius: 0.5rem;
+    border-radius: 0.5rem;
+
+    background-color: rgba(0, 0, 0, 0.1); 
+}
+.sys-cpf-submenu::-webkit-scrollbar-thumb {
+    -webkit-border-radius: 0.5rem;
+    border-radius: 0.5rem;
+
+    background: rgba(255, 255, 255, 0.9); 
+    -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5);
+}
+.sys-cpf-submenu::-webkit-scrollbar-thumb:window-inactive {
+    background: rgba(255, 255, 255, 0.4); 
+}
+.sys-cpf-submenu-cnt ul {
+    display: -webkit-flex;
+    display: flex;
+}
+.sys-cpf-submenu-cnt ul li {
+    -webkit-flex: 0 1 auto; 
+    -ms-flex:  0 1 auto; 
+    flex:  0 1 auto;
+    box-sizing: border-box;
+    height: 3.125rem;
+
+    border-bottom: 2px solid rgba(0,0,0,0.1);
+    border-radius: 0;
+
+    line-height: 3.125rem;
+    white-space: nowrap;
+    
+}
+.sys-cpf-submenu-cnt ul li div {
+    padding: 0 0.625rem;
+}
+.sys-cpf-submenu-cnt ul li.bx-menu-inter-act {
+    border-bottom-color: #364f6b;
+}
+.sys-cpf-submenu-cnt ul li.bx-menu-inter-act > div {
+    font-weight: bold;
+}
+.sys-cpf-submenu-item .bx-cnc-psi-icon,
+.sys-cpf-submenu-item .bx-cnc-psi-icon img {
+    width: 1.5rem;
+    height: 1.5rem;
+}
+.sys-cpf-submenu-item .bx-cnc-psi-icon img {
+    color: #002e74;
+}
+
+.sys-cpf-submenu-item .bx-cnc-psi-icon .sys-icon {
+    vertical-align: middle;
+    font-size: 1.5rem;
+}
+.sys-cpf-submenu-item .bx-cnc-psi-title {
+	text-align: center;
+}
+.sys-cpf-submenu-item.bx-cnc-psi-main .bx-cnc-psi-title {
+    text-align: left;
+}
+.sys-cpf-form {
+	position: relative;
+}
+.sys-cpf-form .sys-cpf-user {
+	position: absolute;
+	top: 0;
+	left: 0;
+}
+.sys-cpf-form .sys-cpf-content {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.sys-cpf-form.sys-cpf-default .sys-cpf-content {
+    -webkit-align-items: stretch;
+    align-items: stretch;   
+}
+.sys-cpf-form.sys-cpf-default .sys-cpf-content > div {
+    width: 100%;
+    cursor: text;
+}
+.sys-cpf-form .bx-uploader-ghost .bx-base-general-icon-wrapper img {
+	max-width: 9.375rem;
+}
+.sys-cpf-close {
+	position: absolute;
+	display: none;
+
+	right: 1.25rem;
+	bottom: 1.25rem;
+}
+
+
+/* --------------- Account Unit */
+.bx-account-unit .bx-def-unit-img, 
+.bx-account-unit-wo-info .bx-def-unit-img {
+    position: relative;
+    overflow: visible;
+}
+
+
+/* --------------- Author Info */
+.sys-author-info {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+}
+.sys-author-profile ul.bx-menu-custom.bx-menu-hor {
+    line-height: 0.625rem;
+}
+.sys-author-profile ul.bx-menu-custom.bx-menu-hor > li.bx-menu-item-nl {
+    width: 100%;
+}
+.sys-author-profile .bx-menu-custom.bx-menu-hor > li > span {
+    margin-right: 0;
+}
+.sys-author-profile .bx-menu-custom.bx-menu-hor > li > span.bx-base-profile-unit-meta-item.bx-def-padding-thd-leftright {
+    padding-left: 0;
+    padding-right: 0.5rem;
+}
+.sys-author-profile .bx-menu-custom.bx-menu-hor > li:first-child .bx-base-general-unit-meta-div {
+    display: none;
+}
+
+/* --------------- Author Info */
+
+video.bx-player-embed {
+    width:100vw;
+    height:100vh;
+}

+ 865 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/css/menu.css

@@ -0,0 +1,865 @@
+/* --------------- horizontal menu */
+
+ul.bx-menu-hor {
+    padding:0;
+    margin:0;
+    list-style:none;
+}
+
+.bx-menu-hor li {
+    display:inline-block;
+    vertical-align:middle;
+}
+
+.bx-menu-hor li a {
+    display:block;
+    text-decoration:none;
+}
+
+.bx-menu-hor-inline li a {
+	display:inline;
+}
+
+.bx-menu-hor li.bx-menu-tab-active a {
+    color:#000;
+}
+
+.bx-menu-hor li img,
+.bx-menu-hor li .sys-icon {
+	display:inline-block;
+    vertical-align:middle;
+}
+.bx-menu-hor li img {
+    height:1.6rem;
+    width:auto;
+
+    background-position: center center;
+    background-repeat: no-repeat;
+}
+
+/* --------------- horizontal inline menu with 'special' buttons */
+.bx-menu-hor-inline-sbtn li a {
+    position: relative;
+    display: inline-block;
+
+    height: 24px; 
+    height: 1.6rem;
+
+    line-height: 24px;
+    line-height: 1.6rem;
+}
+
+/* --------------- vertical, vertical lite, notifications menu */
+
+ul.bx-menu-notif,
+ul.bx-menu-verlit,
+ul.bx-menu-ver,
+ul.bx-menu-custom-ver {
+    padding:0;
+    margin:0;
+    list-style:none;
+}
+
+.bx-menu-ver li,
+.bx-menu-custom-ver li {
+    margin-top:1px;
+}
+
+.bx-menu-notif li:first-child,
+.bx-menu-verlit li:first-child,
+.bx-menu-ver li:first-child,
+.bx-menu-custom-ver li:first-child {
+    margin-top:0px;
+}
+
+.bx-menu-verlit li a,
+.bx-menu-ver li a {
+    display:block;
+    text-decoration:none;
+}
+
+.bx-menu-notif li.bx-menu-tab-active a,
+.bx-menu-verlit li.bx-menu-tab-active a,
+.bx-menu-ver li.bx-menu-tab-active a {
+    color:#000;
+}
+
+.bx-menu-notif li img,
+.bx-menu-notif li .sys-icon,
+.bx-menu-notif li span,
+.bx-menu-verlit li img,
+.bx-menu-verlit li .sys-icon,
+.bx-menu-verlit li span,
+.bx-menu-ver li img,
+.bx-menu-ver li .sys-icon,
+.bx-menu-ver li span {
+    display:inline-block;
+    vertical-align:middle;
+}
+
+.bx-menu-notif li img,
+.bx-menu-notif li .sys-icon,
+.bx-menu-verlit li img,
+.bx-menu-verlit li .sys-icon,
+.bx-menu-ver li img,
+.bx-menu-ver li .sys-icon {
+    width:20px;
+    width:1.25rem;
+    height:20px;
+    height:1.25rem;
+    margin-right:2px;
+    margin-right:0.125rem;
+    text-align:center;
+    vertical-align: top;
+}
+
+.bx-menu-notif li img,
+.bx-menu-verlit li img,
+.bx-menu-ver li img {
+    background-size:1.25rem 1.25rem;
+    background-position:center center;
+    background-repeat:no-repeat;
+}
+
+.bx-menu-ver li .sys-icon {
+    font-size:20px;
+    font-size:1.25rem;
+}
+
+.bx-menu-ver li .flag-icon {
+    vertical-align:baseline;
+}
+
+.bx-menu-notif li .sys-icon {
+    font-size:24px;
+    font-size:1.5rem;
+    width:24px;
+    width:1.5rem;
+    height:24px;
+    height:1.5rem;
+    margin-right:5px;
+    margin-right:0.3rem;
+}
+
+.bx-menu-ver .bx-def-hr {
+    margin:0;
+}
+
+.bx-menu-verlit li img {
+    width:24px;
+    width:1.5rem;
+    height:24px;
+    height:1.5rem;
+    background-position:center center;
+    background-repeat:no-repeat;
+    vertical-align:middle;
+}
+
+.bx-menu-notif .bx-def-unit-alert-middle {
+    position:relative;
+    float:right;
+}
+
+/* --------------- main menu */
+
+#bx-homepage-menu-sys_homepage {
+    text-align:center;
+}
+
+#bx-menu-main-submenu {
+    line-height:2.5rem;
+    vertical-align:top;
+}
+
+.bx-menu-main-submenu-div .sys-icon {
+    font-size:32px;
+    font-size:2rem;
+}
+
+ul.bx-menu-main-submenu {
+    padding:0;
+    margin:0;
+    list-style:none;
+    white-space:nowrap;
+}
+
+.bx-menu-main-submenu li {
+    display:inline-block;
+    margin:0;    
+    vertical-align:top;
+}
+
+.bx-menu-main-submenu li a {
+    display:inline-block;
+    text-decoration:none;
+    font-size:1.2rem;
+    line-height:2.5rem;
+    vertical-align:top;
+}
+
+.bx-menu-main-submenu li.bx-menu-tab-active a {
+    color:#000;
+}
+
+/* --------------- toolbar menu */
+.bx-menu-toolbar {
+    list-style:none;
+    margin:0 0 0 0;
+    padding:0;
+}
+ul.bx-menu-toolbar li {
+	position:relative;
+    float:left;
+    margin:0px;
+} 
+
+ul.bx-menu-toolbar li a {
+    position:relative;
+    display:block;
+    height:48px;
+    height:3rem;
+
+    line-height:48px;
+    line-height:3rem;
+    font-size:16px;
+    font-size:1rem;
+    text-align: center;
+    vertical-align:middle;
+    text-decoration:none;
+}
+
+ul.bx-menu-toolbar li a .sys-icon,
+ul.bx-menu-toolbar li a .sys-icon-a,
+ul.bx-menu-toolbar li a img,
+ul.bx-menu-toolbar li a span {
+    vertical-align:top;
+}
+
+ul.bx-menu-toolbar li a .sys-icon-a {
+    margin-top:0.75rem;
+}
+
+ul.bx-menu-toolbar li img {
+	max-height:48px;
+    max-height:3rem;
+
+    background-position:center center;
+    background-repeat:no-repeat;
+}
+
+ul.bx-menu-toolbar li .sys-icon-a,
+ul.bx-menu-toolbar li .sys-icon {
+    display:inline-block;
+    vertical-align:middle;
+
+    font-size:24px;
+    font-size:1.5rem;
+    text-align:center;
+    width:1.5rem;
+    height:1.5rem;
+}
+
+ul.bx-menu-toolbar li .bx-menu-toolbar-item-unit {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+ul.bx-menu-toolbar li .bx-menu-toolbar-item-unit .bx-base-pofile-unit-online {
+    display: none;
+}
+
+ul.bx-menu-toolbar li a .bx-def-unit-alert-middle {
+    right: 5px;
+    right: -0.3125rem;
+}
+
+.bx-menu-toolbar-padding {
+	padding-top: 48px;
+	padding-top: 3rem;
+}
+
+#bx-menu-toolbar-1-container .bx-menu-toolbar {
+    float:left;
+}
+
+#bx-menu-toolbar-2-container .bx-menu-toolbar {
+    float:right;
+}
+
+#bx-menu-toolbar-2-container .bx-menu-toolbar .bx-menu-toolbar-item-title {
+	display: inline-block;
+	max-width: 10.0rem;
+
+	white-space: nowrap;
+	text-overflow:ellipsis;
+    overflow:hidden;
+}
+
+/* --------------- floating blocks menu */
+.bx-sliding-menu-close,
+.bx-sliding-menu-close:hover {
+    position:absolute;
+    top:0;
+    right:0;
+    text-decoration:none;
+}
+
+.bx-sliding-menu,
+.bx-sliding-menu-main {
+    width:100%;    
+
+    background-color: rgba(255, 255, 255, 0.9);
+
+    overflow-x:auto;
+    -webkit-overflow-scrolling:touch;
+}
+
+.bx-sliding-menu-fullheight {
+    height:calc(100% - 3rem);
+    overflow-x:hidden;
+    overflow-y:auto;
+}
+
+.bx-sliding-menu > .bx-def-page-width,
+.bx-sliding-menu-main > .bx-def-page-width {
+    position: relative;
+}
+
+.bx-sliding-menu-main ul.bx-menu-floating-blocks-big {
+    display:table;
+    margin:0 auto;
+    white-space:nowrap;
+}
+
+.bx-sliding-menu-main .bx-menu-account-popup-profile .bx-base-pofile-unit {
+    margin-bottom:0;
+}
+
+.bx-popup-content .bx-menu-floating-blocks-wide,
+.bx-popup-content .bx-menu-floating-blocks {
+    width:256px;
+    width:16rem;
+}
+.bx-popup-content .bx-menu-account-popup .bx-menu-floating-blocks-wide,
+.bx-popup-content .bx-menu-account-popup .bx-menu-floating-blocks {
+    width:auto;
+}
+
+.bx-menu-account-popup-menu ul.bx-menu-floating-blocks-wide {
+    display:inline-block;
+    max-width:800px;
+    max-width:50rem;
+}
+ul.bx-menu-floating-blocks-dash{
+    display: flex;
+    flex-wrap: wrap;
+    align-content: space-between;
+}
+ul.bx-menu-floating-blocks-wide,
+ul.bx-menu-floating-blocks-big,
+ul.bx-menu-floating-blocks-dash,
+ul.bx-menu-floating-blocks {
+    padding:0;
+    margin:0;
+    list-style:none;
+}  
+
+.bx-menu-floating-blocks-big li,
+
+.bx-menu-floating-blocks-wide li,
+.bx-menu-floating-blocks li {
+    display: inline-block;
+    /* For IE 7 */
+    zoom: 1;
+    *display: inline;
+    vertical-align:top;    
+
+    width:80px;
+    width:5rem;
+    height:80px;
+    height:5rem;
+}
+.bx-menu-floating-blocks-big li {
+    width:128px;
+    width:8rem;
+    height:128px;
+    height:8rem;
+}
+.bx-menu-floating-blocks-dash li {
+    display: block;
+    width:25%;
+    height:3.35rem;
+}
+.bx-media-phone .bx-menu-floating-blocks-dash li {
+    width:49.5%;
+}
+.bx-menu-floating-blocks-dash-item A{
+    display:flex !important;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: center
+}
+.bx-menu-floating-blocks-dash-title{
+    display:flex;
+    width:calc(100% - 3rem);
+    height:2.35rem;
+    flex-direction: column;
+    justify-content: center;
+}
+.bx-media-phone .bx-menu-floating-blocks-title {
+    font-size:0.75rem;
+}
+.bx-menu-floating-blocks-wide li {
+    position:relative;
+    width:100px;
+    width:6.25rem;
+}
+.bx-menu-floating-blocks-wide li .bx-menu-item-addon {
+    right:10px;
+    right:0.625rem;
+    bottom:auto;
+    top:0;    
+}
+
+.bx-menu-floating-blocks-big li:hover,
+.bx-menu-floating-blocks-dash li:hover,
+.bx-menu-floating-blocks-wide li:hover,
+.bx-menu-floating-blocks li:hover {
+    opacity:0.8;
+}
+
+.bx-menu-floating-blocks-big li:active,
+.bx-menu-floating-blocks-big li.bx-menu-tab-active,
+.bx-menu-floating-blocks-dash li:active,
+.bx-menu-floating-blocks-dash li.bx-menu-tab-active,
+.bx-menu-floating-blocks-wide li:active,
+.bx-menu-floating-blocks-wide li.bx-menu-tab-active,
+.bx-menu-floating-blocks li:active,
+.bx-menu-floating-blocks li.bx-menu-tab-active {
+    opacity:1.0;
+}
+
+.bx-menu-floating-blocks-big li a,
+.bx-menu-floating-blocks-dash li a,
+.bx-menu-floating-blocks-wide li a,
+.bx-menu-floating-blocks li a {
+    display:block;
+    text-decoration:none;
+}
+
+.bx-menu-floating-blocks-big li.bx-menu-tab-active a,
+.bx-menu-floating-blocks-dash li.bx-menu-tab-active a,
+.bx-menu-floating-blocks-wide li.bx-menu-tab-active a,
+.bx-menu-floating-blocks li.bx-menu-tab-active a {
+    color:#000;
+}
+
+.bx-menu-floating-blocks li img,
+.bx-menu-floating-blocks-wide li img,
+.bx-menu-floating-blocks li .sys-icon,
+.bx-menu-floating-blocks-wide li .sys-icon {
+    display:block;
+    height:32px;
+    height:2rem;
+    margin:16px 0 3px 0;
+    margin:1rem 0 0.2rem 0;
+}
+
+.bx-menu-floating-blocks li img,
+.bx-menu-floating-blocks-wide li img {
+    width:32px;
+    width:2rem;
+    margin:16px 20px 3px 20px;
+    margin:1rem 1.5rem 0.2rem 1.5rem;
+    background-size:2rem 2rem;
+}
+
+.bx-menu-floating-blocks-big li img,
+.bx-menu-floating-blocks-big li .sys-icon {
+    display:block;
+    height:64px;
+    height:4rem;
+    margin:16px 0 8px 0;
+    margin:1rem 0 0.5rem 0;
+}
+
+.bx-menu-floating-blocks-big li img {
+    width:64px;
+    width:4rem;
+    margin:16px 32px 8px 32px;
+    margin:1rem 2rem 0.5rem 2rem;
+    background-size:4rem 4rem;
+}
+
+.bx-menu-floating-blocks-wide li img {
+    margin-left:34px;
+    margin-right:34px;
+    margin-left:2.125rem;
+    margin-right:2.125rem;
+}
+
+.bx-menu-floating-blocks-big li img,
+.bx-menu-floating-blocks-dash li img,
+.bx-menu-floating-blocks-wide li img,
+.bx-menu-floating-blocks li img {
+    background-position:center center;
+    background-repeat:no-repeat;
+}
+
+.bx-menu-floating-blocks-big li .sys-icon,
+.bx-menu-floating-blocks-dash li .sys-icon,
+.bx-menu-floating-blocks-wide li .sys-icon,
+.bx-menu-floating-blocks li .sys-icon {
+    text-align:center;
+    vertical-align:bottom;
+    line-height:32px;
+    line-height:2rem;
+    font-size:32px;
+    font-size:2rem;
+}
+.bx-menu-floating-blocks-big li .sys-icon {
+    line-height:64px;
+    line-height:4rem;
+    font-size:64px;
+    font-size:4rem;
+}
+
+.bx-menu-floating-blocks-dash li .sys-icon {
+    line-height:2rem;
+    font-size:2rem;
+	width: 2.2rem;
+    display: inline-block;
+}
+
+.bx-menu-floating-blocks-big li .bx-menu-floating-blocks-title,
+.bx-menu-floating-blocks-wide li .bx-menu-floating-blocks-title,
+.bx-menu-floating-blocks li .bx-menu-floating-blocks-title {
+    text-align:center;
+    overflow:hidden;
+    text-overflow:ellipsis;
+    white-space:nowrap;
+}
+
+.bx-menu-floating-blocks-dash li .bx-menu-floating-blocks-title {
+    text-align:left;
+    font-weight:bold;
+    overflow:hidden;
+    text-overflow:ellipsis;
+    white-space:nowrap;
+}
+.bx-menu-floating-blocks-dash li .bx-menu-floating-blocks-info {
+    text-align:left;
+    overflow:hidden;
+    text-overflow:ellipsis;
+    white-space:nowrap;
+}
+.bx-menu-floating-blocks-dash li .bx-menu-floating-blocks-info {
+}
+
+
+/* --------------- interactive block menu */
+ul.bx-menu-inter,
+ul.bx-menu-inter-vert {
+	padding:0;
+    margin:0;
+	list-style:none;
+}
+ul.bx-menu-inter li {
+    float:left;
+}
+li.bx-menu-inter-div {
+	padding: 0px 3px;
+}
+li.bx-menu-inter-div:first-child {
+	display: none;
+}
+li.bx-menu-inter-act {}
+li.bx-menu-inter-pas a:hover {
+	text-decoration: none;
+}
+li.bx-menu-inter-hidden {
+	display: none;
+}
+
+/* --------------- horizontal buttons menu */
+
+ul.bx-menu-btns-ver,
+ul.bx-menu-btns-hor,
+ul.bx-menu-btns-icon-hor {
+    padding:0;
+    margin:0;
+    list-style:none;
+}
+ul.bx-menu-btns-ver,
+ul.bx-menu-btns-hor {
+    text-align:center;
+}
+ul.bx-menu-btns-hor li,
+ul.bx-menu-btns-icon-hor li {
+    display:inline;
+    display:inline-block;
+    vertical-align: bottom;
+}
+ul.bx-menu-btns-ver li {
+    text-align:center;
+}
+ul.bx-menu-btns-ver li .bx-btn {
+    float:none;
+    display:inline-block;
+}
+ul.bx-menu-btns-hor li:last-child .bx-btn,
+ul.bx-menu-btns-icon-hor li:last-child .bx-btn {
+    margin-right:0;
+}
+
+/* --------------- account popup menu */
+
+.bx-popup-content .bx-menu-account-popup {
+    min-width:17em;
+}
+
+.bx-menu-account-popup-menu {
+    text-align:center;
+}
+
+.bx-menu-account-popup-profile-switcher-link {
+    text-align: center;
+}
+
+.bx-menu-account-popup-profile-switcher-link a:hover,
+.bx-menu-account-popup-profile-switcher-link a {
+    text-decoration:none;
+}
+
+.bx-menu-account-popup-profile-switcher-link .bx-btn {
+    float: none;
+    display: inline-block;
+    width: 100%;
+    max-width: 20rem;
+}
+
+
+/* --------------- custom menu */
+
+.bx-menu-custom > li > span,
+.bx-menu-custom > li > a {
+    display:inline-block;
+}
+
+.bx-menu-custom.bx-menu-hor > li > span {
+	margin-right: 20px;
+    margin-right: 1.25rem;
+}
+
+
+/*--- Custom Menu with More Auto ---*/
+.bx-menu-main-submenu.bx-menu-more-auto,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto,
+.bx-menu-custom.bx-menu-hor.bx-menu-submenu-more-auto {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+.bx-menu-main-submenu.bx-menu-more-auto,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto {
+    -webkit-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+
+    overflow: hidden;
+}
+.bx-menu-custom.bx-menu-hor.bx-menu-submenu-more-auto {
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+
+    min-width: 18rem;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item {
+    position: relative;
+    display: block;
+
+    padding: 8px;
+    padding: 0.5rem;
+
+    text-align: left;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item .sys-icon {
+    display: inline-block;
+    vertical-align: top;
+    width: 1.6rem;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item.bx-menu-item-more-auto > span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-menu-item-more-auto > span {
+    display: block;
+    margin: 0;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item > a:last-child,
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item > a.bx-btn:last-child,
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item > span:last-child,
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item:last-child > a,
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item:last-child > a.bx-btn,
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item:last-child > span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item > a:last-child,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item > a.bx-btn:last-child,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item > span:last-child,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item:last-child > a,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item:last-child > a.bx-btn,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item:last-child > span {
+    margin-right: 0;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-item.bx-menu-item-more-auto,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-menu-item-more-auto {
+    display: none;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .sys-icon {
+    display: inline-block;
+    vertical-align: middle;
+    width: 2.0rem;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .bx-btn,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .bx-btn {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+
+    height: auto;
+    line-height: auto;
+
+    margin: 0;
+    padding: 0;
+
+    background-color: transparent;
+    border-width: 0;
+    border-radius: 0;
+
+    box-shadow: none;
+    text-shadow: none;
+}
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .sys-icon,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .sys-icon,
+.bx-menu-main-submenu.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .bx-btn,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item .bx-btn {
+    color: #3e8685;
+}
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-view-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-vote-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-score-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-favorite-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-feature-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-report-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-tl-repost-element-holder a span {
+    display: none;
+}
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-view-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-vote-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-score-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-favorite-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-feature-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-report-element-holder a span,
+.bx-menu-custom.bx-menu-hor.bx-menu-more-auto .bx-menu-submenu-more-auto .bx-menu-item.bx-base-general-ea-sa .bx-tl-repost-element-holder a span {
+    display: inline;
+}
+
+/*--- Vertical (Vertical Custom) Menus (in Popup) ---*/
+.bx-popup .bx-menu-custom-ver li a {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+    box-sizing: border-box;
+    width: 100%;
+    padding: 0 0.625rem;
+}
+
+.bx-popup .bx-menu-ver li img,
+.bx-popup .bx-menu-ver li .sys-icon,
+.bx-popup .bx-menu-custom-ver li img,
+.bx-popup .bx-menu-custom-ver li .sys-icon {
+    display: inline-block;
+    vertical-align: middle;
+    width: 1.5rem;
+    height: 1.5rem;
+    line-height: 1.5rem;
+    font-size: 1.5rem;
+}
+
+.bx-popup .bx-menu-ver li .sys-icon:before,
+.bx-popup .bx-menu-custom-ver li .sys-icon:before {
+    display: block;
+    text-align: center;
+}
+
+
+/* --------------- profile stats menu */
+.bx-menu-ver.bx-menu-object-sys_profile_stats li {
+    border-width: 0;
+    border-radius: 0;
+}
+.bx-menu-ver.bx-menu-object-sys_profile_stats li .bx-menu-item-cnt {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+.bx-menu-ver.bx-menu-object-sys_profile_stats li a {
+    -webkit-flex-grow: 3;
+    flex-grow: 3;
+
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-flow: row nowrap;
+    flex-flow: row nowrap;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+
+    line-height: 1.5rem;
+}
+.bx-menu-ver.bx-menu-object-sys_profile_stats li img, 
+.bx-menu-ver.bx-menu-object-sys_profile_stats li .sys-icon {
+    display: inline-block;
+    vertical-align: top;
+    width: 1.0rem;
+    height: 1.0rem;
+    line-height: 1.0rem;
+    font-size: 1.0rem;
+}
+
+/* --------------- add relation menu */
+.bx-popup .bx-menu-ver.bx-menu-object-sys_add_relation li a {
+    line-height: normal;
+}
+.bx-popup .bx-menu-ver.bx-menu-object-sys_add_relation li a span {
+    margin-left: 0;
+    text-transform: capitalize;
+}
+
+.bx-menu-add-relation-hidden {
+    display: none;
+}
+
+/* --------------- switch language menu (popup) */
+.bx-popup .bx-menu-ver.bx-menu-object-sys_switch_language_popup li a div > span {
+    margin-left: 0;
+}
+.bx-popup ul.bx-menu-ver.bx-menu-object-sys_switch_language_popup li span.flag-icon {
+    margin-right: 0.5rem;
+}

+ 21 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/menu_account_popup.html

@@ -0,0 +1,21 @@
+<div id="bx-sliding-menu-account" class="bx-sliding-menu-main bx-sliding-menu-fullheight bx-def-z-index-nav bx-def-color-bg-block bx-def-border-bottom bx-def-box-sizing bx-search-site" style="display:none; opacity:1;">
+    <div class="bx-def-page-width bx-def-centered">
+        <div class="bx-sliding-menu-main-cnt">
+            <a class="bx-sliding-menu-close bx-def-margin-sec-top bx-def-margin-right bx-def-font-h1" href="javascript:void(0);" onclick="bx_menu_slide_inline('#bx-sliding-menu-account')"><i class="sys-icon times"></i></a>
+
+            <div class="bx-menu-account-popup bx-menu-object-__object__ bx-def-padding-top bx-def-padding-leftright">
+                <div class="bx-menu-account-popup-profile bx-clearfix">__active_profile__</div>
+                <div class="bx-menu-account-popup-menu bx-def-margin-sec-top">__menu_notifications__</div>
+            </div>
+            <div class="bx-menu-account-popup-profile-switcher bx-def-box-sizing">__profile_switcher__</div>
+            <bx_if:multiple_profiles_mode>
+                <hr class="bx-def-hr" />
+                <div class="bx-menu-account-popup-profile-switcher-link bx-def-padding">
+                    <a class="bx-btn" href="__url_switch_profile__">
+                        <i class="sys-icon plus"></i><span class="bx-def-padding-sec-left"><bx_text:_sys_txt_my_profiles /></span>
+                    </a>
+                </div>
+            </bx_if:multiple_profiles_mode>
+        </div>
+    </div>
+</div>

+ 17 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/menu_vertical.html

@@ -0,0 +1,17 @@
+<ul class="bx-menu-ver bx-menu-object-__object__ bx-clearfix">
+    <bx_repeat:menu_items>
+        <li class="bx-def-round-corners bx-def-color-bg-hl-hover __class_add__">
+            <a href="__link__" target="__target__" onclick="__onclick__">
+                <div class="bx-def-padding-sec">
+                    <bx_if:icon><i class="sys-icon __icon__"></i></bx_if:icon><bx_if:image><img style="background-image:url(__icon_url__)" src="<bx_icon_url:spacer.gif />" /></bx_if:image><span>__title__</span>
+                </div>
+            </a>
+        </li>
+    </bx_repeat:menu_items>
+</ul>
+<script language="javascript">
+    $(document).ready(function () {
+        // add default class to active menu items
+        $('.bx-menu-ver > li.bx-menu-tab-active').not('.bx-def-color-bg-active').addClass('bx-def-color-bg-active');
+    });
+</script>

+ 1144 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseCmts.php

@@ -0,0 +1,1144 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * @see BxDolCmts
+ */
+class BxBaseCmts extends BxDolCmts
+{
+    protected static $_sTmplContentElementBlock;
+    protected static $_sTmplContentElementInline;
+    protected static $_sTmplContentDoCommentLabel;
+    protected static $_sTmplContentCounter;
+
+    protected $_sTmplNameItem;
+
+    protected $_sJsObjClass;
+    protected $_sJsObjName;
+    protected $_sStylePrefix;
+
+    protected $_aHtmlIds;
+
+    protected $_aElementDefaults;
+
+    function __construct( $sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+        if (empty($sSystem))
+            return;
+
+        $this->_sTmplNameItem = 'comment.html';
+                
+        $this->_sJsObjClass = 'BxDolCmts';
+        $this->_sJsObjName = 'oCmts' . bx_gen_method_name($sSystem, array('_' , '-')) . $iId;
+        $this->_sStylePrefix = isset($this->_aSystem['root_style_prefix']) ? $this->_aSystem['root_style_prefix'] : 'cmt';
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $sSystem) . '-' . $iId;
+
+        $this->_aHtmlIds = array(
+            'main' => 'bx-cmt-' . $sHtmlId,
+            'counter' => 'bx-cmt-counter-' . $sHtmlId
+        );
+
+        $this->_aElementDefaults = array(
+            'show_do_comment_as_button' => false,
+            'show_do_comment_as_button_small' => false,
+            'show_do_comment_image' => false,
+            'show_do_comment_icon' => true,
+            'show_do_comment_label' => false,
+            'show_counter' => true,
+            'show_counter_empty' => false
+        );
+
+        if(empty(self::$_sTmplContentElementBlock))
+            self::$_sTmplContentElementBlock = $this->_oTemplate->getHtml('comment_element_block.html');
+
+        if(empty(self::$_sTmplContentElementInline))
+            self::$_sTmplContentElementInline = $this->_oTemplate->getHtml('comment_element_inline.html');
+
+        if(empty(self::$_sTmplContentDoCommentLabel))
+            self::$_sTmplContentDoCommentLabel = $this->_oTemplate->getHtml('comment_do_comment_label.html');
+
+        if(empty(self::$_sTmplContentCounter))
+            self::$_sTmplContentCounter = $this->_oTemplate->getHtml('comment_counter.html');
+
+        $this->_oTemplate->addJsTranslation('_sys_txt_cmt_loading');
+    }
+
+    /**
+     * Add comments CSS/JS
+     */
+    public function addCssJs ()
+    {
+        $oForm = BxDolForm::getObjectInstance($this->_sFormObject, $this->_sFormDisplayPost);
+        $oForm->addCssJs();
+    }
+
+    public function getJsObjectName()
+    {
+        return $this->_sJsObjName;
+    }
+
+    /**
+     * Get initialization section of comments box
+     *
+     * @return string
+     */
+    public function getJsScript($aBp = array(), $aDp = array())
+    {
+        $bMinPostForm = isset($aDp['min_post_form']) ? $aDp['min_post_form'] : $this->_bMinPostForm;
+
+        $aParams = array(
+            'sObjName' => $this->_sJsObjName,
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'sSystem' => $this->getSystemName(),
+            'iAuthorId' => $this->_getAuthorId(),
+            'iObjId' => $this->getId(),
+            'sBaseUrl' => $this->getBaseUrl(),
+            'sPostFormPosition' => $this->_aSystem['post_form_position'],
+            'sBrowseType' => $this->_sBrowseType,
+            'sDisplayType' => $this->_sDisplayType,
+            'iMinPostForm' => $bMinPostForm ? 1 : 0,
+            'sStylePrefix' => $this->_sStylePrefix,
+        );
+
+        $this->addCssJs();
+        return $this->_oTemplate->_wrapInTagJsCode("if(window['" . $this->_sJsObjName . "'] == undefined) var " . $this->_sJsObjName . " = new " . $this->_sJsObjClass . "(" . json_encode($aParams) . "); " . $this->_sJsObjName . ".cmtInit();");
+    }
+
+    /**
+     * get full comments block with initializations
+     */
+    function getCommentsBlock($aBp = array(), $aDp = array())
+    {
+        $mixedResult = $this->isViewAllowed();
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+        
+        $this->_getParams($aBp, $aDp);
+
+        //add live update
+        $this->actionResumeLiveUpdate();
+
+        $sServiceCall = BxDolService::getSerializedService('system', 'get_live_update', array($this->_sSystem, $this->_iId, $this->_getAuthorId(), '{count}'), 'TemplCmtsServices');
+        BxDolLiveUpdates::getInstance()->add($this->_sSystem . '_live_updates_cmts_' . $this->_iId, 1, $sServiceCall);
+        //add live update
+
+        $sCaption = _t($this->_aT['block_comments_title'], $this->getCommentsCountAll());
+        $sContent = $this->_oTemplate->parseHtmlByName('comments_block.html', array(
+            'system' => $this->_sSystem,
+            'list_anchor' => $this->getListAnchor(),
+            'id' => $this->getId(),
+            'comments' => $this->getComments($aBp, $aDp),
+            'post_form_top' => $this->getFormBoxPost($aBp, array_merge($aDp, array('type' => $this->_sDisplayType, 'position' => BX_CMT_PFP_TOP))),
+            'post_form_bottom'  => $this->getFormBoxPost($aBp, array_merge($aDp, array('type' => $this->_sDisplayType, 'position' => BX_CMT_PFP_BOTTOM))),
+            'view_image_popup' => $this->_getViewImagePopup(),
+            'script' => $this->getJsScript($aBp, $aDp)
+        ));
+
+        return $aDp['in_designbox'] ? DesignBoxContent($sCaption, $sContent, BX_DB_DEF, $this->_getControlsBox()) : array(
+            'title' => $sCaption,
+            'content' => $sContent,
+            'menu' => $this->_getControlsBox(),
+        );
+    }
+
+    /**
+     * get comments list for specified parent comment
+     *
+     * @param array $aBp - browse params array
+     * @param array $aDp - display params array
+     *
+     */
+    function getComments($aBp = array(), $aDp = array())
+    {
+        $this->_prepareParams($aBp, $aDp);
+
+        $aCmts = $this->getCommentsArray($aBp['vparent_id'], $aBp['filter'], $aBp['order'], $aBp['start'], $aBp['per_view']);
+        if(empty($aCmts) || !is_array($aCmts)) {
+        	if((int)$aBp['parent_id'] == 0 && !isLogged())	{
+        		$oPermalink = BxDolPermalinks::getInstance();
+        		return MsgBox(_t('_cmt_msg_login_required', $oPermalink->permalink('page.php?i=login'), $oPermalink->permalink('page.php?i=create-account')));
+        	}
+
+            return isset($aDp['show_empty']) && $aDp['show_empty'] === true ? $this->_getEmpty($aDp) : '';
+        }
+
+        $sCmts = '';
+        foreach($aCmts as $k => $aCmt)
+            $sCmts .= $this->getComment($aCmt, $aBp, $aDp);
+
+        $sCmts = $this->_getMoreLink($sCmts, $aBp, $aDp);
+        return $sCmts;
+    }
+
+    /**
+     * get comment view block with initializations
+     */
+    function getCommentBlock($iCmtId = 0, $aBp = array(), $aDp = array())
+    {
+        $mixedResult = $this->isViewAllowed();
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        $aBp = array_merge(array('type' => $this->_sBrowseType), $aBp);
+        $aDp = array_merge(array('type' => BX_CMT_DISPLAY_THREADED), $aDp);
+        $sComment = $this->getComment($iCmtId, $aBp, $aDp);
+        if (!$sComment)
+            return '';
+
+        return $this->_oTemplate->parseHtmlByName('comment_block.html', array(
+            'system' => $this->_sSystem,
+            'id' => $this->getId(),
+            'comment' => $sComment,
+            'view_image_popup' => $this->_getViewImagePopup(), 
+            'script' => $this->getJsScript($aBp, $aDp)
+        ));
+    }
+
+    /**
+     * get one just posted comment
+     *
+     * @param  int    $iCmtId - comment id
+     * @return string
+     */
+    function getComment($mixedCmt, $aBp = array(), $aDp = array())
+    {
+        $iUserId = $this->_getAuthorId();
+        $aCmt = !is_array($mixedCmt) ? $this->getCommentRow((int)$mixedCmt) : $mixedCmt;
+        if (!$aCmt)
+            return '';
+
+        list($sAuthorName, $sAuthorLink, $sAuthorIcon) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+
+        $sClass = $sClassCnt = '';
+        if(isset($aCmt['vote_rate']) && (float)$aCmt['vote_rate'] < $this->_aSystem['viewing_threshold']) {
+            $this->_oTemplate->pareseHtmlByName('comment_hidden.html', array(
+                'js_object' => $this->_sJsObjName,
+                'id' => $aCmt['cmt_id'],
+                'title' => bx_process_output(_t('_hidden_comment', $sAuthorName)),
+                'bx_if:show_replies' => array(
+                    'condition' => $aCmt['cmt_replies'] > 0,
+                    'content' => array(
+                        'replies' => _t('_Show N replies', $aCmt['cmt_replies'])
+                    )
+                )
+            ));
+
+            $sClass = ' ' . $this->_sStylePrefix . '-hidden';
+        }
+
+        if($aCmt['cmt_author_id'] == $iUserId)
+            $sClass .= ' ' . $this->_sStylePrefix . '-mine';
+
+        if(!empty($aDp['blink']) && in_array($aCmt['cmt_id'], $aDp['blink']))
+            $sClass .= ' ' . $this->_sStylePrefix . '-blink';
+
+        if(!empty($aDp['class_comment']))
+            $sClass .= ' ' . $aDp['class_comment'];
+
+        if(!empty($aDp['class_comment_content']))
+            $sClassCnt .= ' ' . $aDp['class_comment_content'];
+
+        $sActions = $this->_getActionsBox($aCmt, $aDp);
+
+        $aTmplReplyTo = array();
+        if((int)$aCmt['cmt_parent_id'] != 0) {
+            $aParent = $this->getCommentRow($aCmt['cmt_parent_id']);
+            list($sParAuthorName, $sParAuthorLink, $sParAuthorIcon) = $this->_getAuthorInfo($aParent['cmt_author_id']);
+
+            $aTmplReplyTo = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'par_cmt_link' => $this->getItemUrl($aCmt['cmt_parent_id']),
+            	'par_cmt_title' => bx_html_attribute(_t('_in_reply_to', $sParAuthorName)),
+                'par_cmt_author' => $sParAuthorName
+            );
+        }
+
+        $sAttachments = $this->_getAttachments($aCmt);
+
+        $sReplies = '';
+        if((int)$aCmt['cmt_replies'] > 0 && !empty($aDp) && $aDp['type'] == BX_CMT_DISPLAY_THREADED) {
+        	$aDp['show_empty'] = false;
+            $sReplies = $this->getComments(array('parent_id' => $aCmt['cmt_id'], 'vparent_id' => $aCmt['cmt_id'], 'type' => $aBp['type']), $aDp);
+        }
+
+        $aTmplVarsMeta = array();
+        if(!empty($this->_sMenuObjMeta)) {
+            $oMenuMeta = BxDolMenu::getObjectInstance($this->_sMenuObjMeta, $this->_oTemplate);
+            if($oMenuMeta) {
+                $oMenuMeta->setCmtsData($this, $aCmt['cmt_id']);
+
+                $aTmplVarsMeta = array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'meta' => $oMenuMeta->getCode()
+                );
+            }
+        }
+
+        $sReactions = '';
+        if(($oReaction = $this->getReactionObject($aCmt['cmt_unique_id'])) !== false)
+            $sReactions = $oReaction->getCounter(array(
+                'show_counter' => true
+            ));
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameItem, array_merge(array(
+            'system' => $this->_sSystem,
+            'style_prefix' => $this->_sStylePrefix,
+            'js_object' => $this->_sJsObjName,
+            'id' => $aCmt['cmt_id'],
+            'anchor' => $this->getItemAnchor($aCmt['cmt_id']),
+            'class' => $sClass,
+            'class_cnt' => $sClassCnt,
+            'bx_if:show_reply_to' => array(
+                'condition' => !empty($aTmplReplyTo),
+                'content' => $aTmplReplyTo
+            ),
+            'bx_if:meta' => array(
+                'condition' => !empty($aTmplVarsMeta),
+                'content' => $aTmplVarsMeta
+            ),
+            'bx_if:show_attached' => array(
+                'condition' => !empty($sAttachments),
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'attached' => $sAttachments
+                )
+            ),
+            'reactions' => $sReactions,
+            'actions' => $sActions,
+            'replies' =>  $sReplies,
+        ), $this->_getTmplVarsAuthor($aCmt), $this->_getTmplVarsText($aCmt)));
+    }
+
+    function getCommentSearch($iCmtId, &$sAddon)
+    {
+        $aBp = array();
+        $aDp = array(
+            'type' => BX_CMT_DISPLAY_FLAT, 
+            'view_only' => true
+        );
+
+        if(empty($sAddon))
+            $sAddon = $this->getJsScript($aBp, $aDp);
+
+        return $this->_oTemplate->parseHtmlByName('comment_search.html', array(
+            'comment' => $this->getComment($iCmtId, $aBp, $aDp),
+            'view_image_popup' => $this->_getViewImagePopup(), 
+        )); 
+    }
+
+    /**
+     * get one comment for "Live Search"
+     *
+     * @param  int    $iCmtId - comment id
+     * @return string
+     */
+    function getCommentLiveSearch($mixedCmt, $aParams = array())
+    {
+        $aCmt = !is_array($mixedCmt) ? $this->getCommentRow((int)$mixedCmt) : $mixedCmt;
+
+        list($sAuthorName, $sAuthorLink, $sAuthorIcon) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+        $bAuthorIcon = !empty($sAuthorIcon);
+
+        $sViewLink = $this->getViewUrl($aCmt['cmt_id']);
+        if(empty($sViewLink))
+            $sViewLink = '#';
+
+        return $this->_oTemplate->parseHtmlByName('comment_live_search.html', array(
+            'bx_if:show_icon' => array(
+                'condition' => $bAuthorIcon,
+                'content' => array(
+                    'author_icon' => $sAuthorIcon,
+        			'view_link' => $sViewLink
+                )
+            ),
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bAuthorIcon,
+                'content' => array()
+            ),
+            'view_link' => $sViewLink,
+            'text' => BxTemplFunctions::getInstance()->getStringWithLimitedLength(strip_tags($aCmt['cmt_text']), $this->_sSnippetLenthLiveSearch),
+            'sample' => isset($aParams['txt_sample_single']) ? _t($aParams['txt_sample_single']) : ''
+        ));
+    }
+
+    function getFormBoxPost($aBp = array(), $aDp = array())
+    {
+        return $this->_getFormBox(BX_CMT_ACTION_POST, $aBp, $aDp);
+    }
+
+    function getFormBoxEdit($aBp = array(), $aDp = array())
+    {
+        return $this->_getFormBox(BX_CMT_ACTION_EDIT, $aBp, $aDp);
+    }
+
+    function getFormPost($iCmtParentId = 0, $aDp = array())
+    {
+        return $this->_getFormPost($iCmtParentId, $aDp);
+    }
+
+    function getFormEdit($iCmtId, $aDp = array())
+    {
+        return $this->_getFormEdit($iCmtId, $aDp);
+    }
+
+    function getLiveUpdate($iCountOld = 0, $iCountNew = 0)
+    {
+        $iCount = (int)$iCountNew - (int)$iCountOld;
+        if($iCount < 0)
+            return '';
+
+        $aComments = $this->_oQuery->getCommentsBy(array('type' => 'latest', 'object_id' => $this->_iId, 'author' => $this->_getAuthorId(), 'others' => 1, 'start' => '0', 'per_page' => $iCount));
+        if(empty($aComments) || !is_array($aComments))
+            return '';
+
+        $aComment = array_shift($aComments);
+        if(empty($aComment) || !is_array($aComment))
+            return '';
+
+        $sJsObject = $this->getJsObjectName();
+        return $this->_oTemplate->parseHtmlByName('comments_lu_button.html', array(
+            'style_prefix' => $this->_sStylePrefix,
+            'html_id' => $this->getNotificationId(),
+            'onclick_show' => "javascript:" . $sJsObject . ".goToBtn(this, '" . $this->getItemAnchor($aComment['cmt_id']) . "', '" . $aComment['cmt_id'] . "');",
+        ));
+    }
+
+    function getLiveUpdates($iCountOld = 0, $iCountNew = 0)
+    {
+        $bShowAll = true;
+        $bShowActions = false;
+
+        $iCount = (int)$iCountNew - (int)$iCountOld;
+        if($iCount < 0)
+            return '';
+
+        $aComments = $this->_oQuery->getCommentsBy(array('type' => 'latest', 'object_id' => $this->_iId, 'author' => $this->_getAuthorId(), 'others' => 1, 'start' => '0', 'per_page' => $iCount));
+        if(empty($aComments) || !is_array($aComments))
+            return '';
+
+        $sJsObject = $this->getJsObjectName();
+
+        $iUserId = $this->_getAuthorId();
+        $bModerator = $this->isModerator();
+
+        $aComments = array_reverse($aComments);
+        $iComments = count($aComments);
+
+        $aTmplVarsNotifs = array();
+        foreach($aComments as $iIndex => $aComment) {
+            $iCommentId = $aComment['cmt_id'];
+
+            $sShowOnClick = "javascript:" . $sJsObject . ".goTo(this, '" .  $this->getItemAnchor($iCommentId) . "', '" . $iCommentId . "');";
+            $sReplyOnClick = "javascript:" . $sJsObject . ".goToAndReply(this, '" . $this->getItemAnchor($iCommentId) . "', '" . $iCommentId . "');";
+
+            $iAuthorId = (int)$aComment['cmt_author_id'];
+            if($iAuthorId < 0) {
+                if(abs($iAuthorId) == $iUserId)
+                    continue;
+                else if($bModerator)
+                    $iAuthorId *= -1;
+            }
+
+            $oAuthor = $this->_getAuthorObject($iAuthorId);
+            $sAuthorName = $oAuthor->getDisplayName();
+
+            $aTmplVarsNotifs[] = array(
+                'bx_if:show_as_hidden' => array(
+                    'condition' => !$bShowAll && $iIndex < ($iComments - 1),
+                    'content' => array(),
+                ),
+                'item' => $this->_oTemplate->parseHtmlByName('comments_lu_notifications.html', array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'onclick_show' => $sShowOnClick,
+                    'onclick_reply' => $sReplyOnClick,
+                    'author_link' => $oAuthor->getUrl(), 
+                    'author_title' => bx_html_attribute($sAuthorName),
+                    'author_name' => $sAuthorName,
+                    'author_unit' => $oAuthor->getUnit(0, array('template' => 'unit_wo_info_links')), 
+                    'text' => _t('_cmt_txt_added_sample')
+                )),
+                'bx_if:show_previous' => array(
+                    'condition' => $bShowActions && $iIndex > 0,
+                    'content' => array(
+                        'onclick_previous' => $sJsObject . '.previousLiveUpdate(this)'
+                    )
+                ),
+                'bx_if:show_close' => array(
+                    'condition' => $bShowActions,
+                    'content' => array(
+                        'onclick_close' => $sJsObject . '.hideLiveUpdate(this)'
+                    )
+                )
+            );
+        }
+
+        return $this->_oTemplate->parseHtmlByName('popup_chain.html', array(
+            'html_id' => $this->getNotificationId(),
+            'bx_repeat:items' => $aTmplVarsNotifs
+        ));
+    }
+
+    public function getElementBlock($aParams = array())
+    {
+        $aParams['usage'] = BX_CMT_USAGE_BLOCK;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElementInline($aParams = array())
+    {
+        $aParams['usage'] = BX_CMT_USAGE_INLINE;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElement($aParams = array())
+    {
+    	$aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        $bShowDoCommentAsButtonSmall = isset($aParams['show_do_comment_as_button_small']) && $aParams['show_do_comment_as_button_small'] == true;
+        $bShowDoCommentAsButton = !$bShowDoCommentAsButtonSmall && isset($aParams['show_do_comment_as_button']) && $aParams['show_do_comment_as_button'] == true;
+        $bShowCounterEmpty = isset($aParams['show_counter_empty']) && $aParams['show_counter_empty'] == true;
+
+        $iObjectId = $this->getId();
+        $iAuthorId = $this->_getAuthorId();
+        $iAuthorIp = $this->_getAuthorIp();
+
+        $iCount = $this->getCommentsCountAll();
+        $bCount = (int)$iCount != 0;
+
+        $isAllowedComment = $this->isPostAllowed();
+
+        //--- Do Comment
+        $bTmplVarsDoComment = $this->_isShowDoComment($aParams, $isAllowedComment, $bCount);
+        $aTmplVarsDoComment = array();
+        if($bTmplVarsDoComment) {
+            $sClass = '';
+            if($bShowDoCommentAsButton)
+                $sClass = 'bx-btn';
+            else if ($bShowDoCommentAsButtonSmall)
+                $sClass = 'bx-btn bx-btn-small';
+
+            if(!$isAllowedComment)
+                $sClass .= $bShowDoCommentAsButton || $bShowDoCommentAsButtonSmall ? ' bx-btn-disabled' : 'bx-cmts-disabled';
+
+            $aTmplVarsDoComment = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'do_comment' => $this->_oTemplate->parseLink($this->getListUrl(), $this->_getLabelDo($aParams), array(
+                    'class' => $this->_sStylePrefix . '-do-comment ' . $this->_sStylePrefix . '-dc ' . $sClass,
+                    'title' => _t($this->_getTitleDo())
+                )),
+            );
+        }
+
+        //--- Counter
+        $bTmplVarsCounter = $this->_isShowCounter($aParams, $isAllowedComment, $bCount);
+
+        $aTmplVarsCounter = array();
+        if($bTmplVarsCounter)
+            $aTmplVarsCounter = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'bx_if:show_hidden' => array(
+                    'condition' => !$bShowCounterEmpty && !$bCount,
+                    'content' => array()
+                ),
+                'counter' => $this->getCounter($aParams)
+            );
+
+        if(!$bTmplVarsDoComment && !$bTmplVarsCounter)
+            return '';
+
+        $sTmplName = $this->{'_getTmplElement' . bx_gen_method_name(!empty($aParams['usage']) ? $aParams['usage'] : BX_DOL_SCORE_USAGE_DEFAULT)}();
+        return $this->_oTemplate->parseHtmlByContent($sTmplName, array(
+            'style_prefix' => $this->_sStylePrefix,
+            'html_id' => $this->_aHtmlIds['main'],
+            'class' => $this->_sStylePrefix . ($bShowDoCommentAsButton ? '-button' : '') . ($bShowDoCommentAsButtonSmall ? '-button-small' : ''),
+            'count' => $iCount,
+            'bx_if:show_do_comment' => array(
+                'condition' => $bTmplVarsDoComment,
+                'content' => $aTmplVarsDoComment
+            ),
+            'bx_if:show_counter' => array(
+                'condition' => $bTmplVarsCounter,
+                'content' => $aTmplVarsCounter
+            ),
+            'script' => ''
+        ));
+    }
+
+    public function getCounter($aParams = array())
+    {
+        $bShowEmpty = isset($aParams['show_counter_empty']) && $aParams['show_counter_empty'] == true;
+        $bShowDoCommentAsButtonSmall = isset($aParams['show_do_comment_as_button_small']) && $aParams['show_do_comment_as_button_small'] == true;
+        $bShowDoCommentAsButton = !$bShowDoCommentAsButtonSmall && isset($aParams['show_do_comment_as_button']) && $aParams['show_do_comment_as_button'] == true;
+
+        $iCount = (int)$this->getCommentsCountAll();
+
+        $sClass = $this->_sStylePrefix . '-counter';
+        if($bShowDoCommentAsButtonSmall)
+            $sClass .= ' bx-btn-small-height';
+        if($bShowDoCommentAsButton)
+            $sClass .= ' bx-btn-height';
+
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplCounter(), array(
+            'id' => $this->_aHtmlIds['counter'],
+            'class' => $sClass,
+            'content' => $iCount != 0 || $bShowEmpty ? $this->_getLabelCounter($iCount) : ''
+        ));
+    }
+
+    protected function _getLabelDo($aParams = array())
+    {
+        return $this->_oTemplate->parseHtmlByContent($this->_getTmplLabelDo(), array(
+            'bx_if:show_image' => array(
+                'condition' => isset($aParams['show_do_comment_image']) && $aParams['show_do_comment_image'] == true,
+                'content' => array(
+                    'src' => $this->_getImageDo()
+                )
+            ),
+            'bx_if:show_icon' => array(
+                'condition' => isset($aParams['show_do_comment_icon']) && $aParams['show_do_comment_icon'] == true,
+                'content' => array(
+                    'name' => $this->_getIconDo()
+                )
+            ),
+            'bx_if:show_text' => array(
+                'condition' => isset($aParams['show_do_comment_label']) && $aParams['show_do_comment_label'] == true,
+                'content' => array(
+                    'text' => _t($this->_getTitleDo())
+                )
+            )
+        ));
+    }
+
+    protected function _getLabelCounter($iCount)
+    {
+        return (int)$iCount != 0 ? _t('_cmt_txt_counter', $iCount) : _t('_cmt_txt_counter_empty');
+    }
+
+    protected function _getTmplElementBlock()
+    {
+        return self::$_sTmplContentElementBlock;
+    }
+
+    protected function _getTmplElementInline()
+    {
+        return self::$_sTmplContentElementInline;
+    }
+
+    protected function _getTmplLabelDo()
+    {
+        return self::$_sTmplContentDoCommentLabel;
+    }
+
+    protected function _getTmplCounter()
+    {
+        return self::$_sTmplContentCounter;
+    }
+
+
+    /**
+     * private functions
+     */
+    protected function _getControlsBox()
+    {
+        $sDisplay = '';
+        $bDisplay = (int)$this->_aSystem['is_display_switch'] == 1;
+        if($bDisplay) {
+            $aDisplayLinks = array(
+                array('id' => $this->_sSystem . '-flat', 'name' => $this->_sSystem . '-flat', 'class' => '', 'title' => '_cmt_display_flat', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeDisplay(this, \'flat\');'),
+                array('id' => $this->_sSystem . '-threaded', 'name' => $this->_sSystem . '-threaded', 'class' => '', 'title' => '_cmt_display_threaded', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeDisplay(this, \'threaded\');')
+            );
+
+            $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sSystem . '-display', 'menu_items' => $aDisplayLinks));
+            $oMenu->setSelected('', $this->_sSystem . '-' . $this->_sDisplayType);
+            $sDisplay = $oMenu->getCode();
+        }
+
+        $sBrowseType = '';
+        $bBrowseType = (int)$this->_aSystem['is_browse_switch'] == 1;
+        if($bBrowseType) {
+            $aBrowseLinks = array(
+                array('id' => $this->_sSystem . '-tail', 'name' => $this->_sSystem . '-tail', 'class' => '', 'title' => '_cmt_browse_tail', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeBrowse(this, \'tail\');'),
+                array('id' => $this->_sSystem . '-head', 'name' => $this->_sSystem . '-head', 'class' => '', 'title' => '_cmt_browse_head', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeBrowse(this, \'head\');'),
+                array('id' => $this->_sSystem . '-popular', 'name' => $this->_sSystem . '-popular', 'class' => '', 'title' => '_cmt_browse_popular', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeBrowse(this, \'popular\');'),
+            );
+
+            $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sSystem . '-browse', 'menu_items' => $aBrowseLinks));
+            $oMenu->setSelected('', $this->_sSystem . '-' . $this->_sBrowseType);
+            $sBrowseType = $oMenu->getCode();
+        }
+
+        $sBrowseFilter = '';
+        $bBrowseFilter = (int)$this->_aSystem['is_browse_filter'] == 1;
+        if($bBrowseFilter) {
+            $aFilterLinks = array(
+                array('id' => $this->_sSystem . '-all', 'name' => $this->_sSystem . '-all', 'class' => '', 'title' => '_cmt_browse_all', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeFilter(this, \'all\');'),
+                array('id' => $this->_sSystem . '-friends', 'name' => $this->_sSystem . '-friends', 'class' => '', 'title' => '_cmt_browse_friends', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeFilter(this, \'friends\');'),
+                array('id' => $this->_sSystem . '-subscriptions', 'name' => $this->_sSystem . '-subscriptions', 'class' => '', 'title' => '_cmt_browse_subscriptions', 'target' => '_self', 'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtChangeFilter(this, \'subscriptions\');')
+            );
+
+            $oMenu = new BxTemplMenuInteractive(array('template' => 'menu_interactive_vertical.html', 'menu_id'=> $this->_sSystem . '-filter', 'menu_items' => $aFilterLinks));
+            $oMenu->setSelected('', $this->_sSystem . '-' . $this->_sBrowseFilter);
+            $sBrowseFilter = $oMenu->getCode();
+        }
+
+        return $this->_oTemplate->parseHtmlByName('comments_controls.html', array(
+            'display_switcher' => $bDisplay ? $sDisplay : '',
+            'bx_if:is_divider_1' => array(
+                'condition' => $bDisplay && $bBrowseType,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                )
+            ),
+            'browse_switcher' => $bBrowseType ? $sBrowseType : '',
+            'bx_if:is_divider_2' => array(
+                'condition' => $bBrowseType && $bBrowseFilter,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                )
+            ),
+            'filter_switcher' => $bBrowseFilter ? $sBrowseFilter : '',
+        ));
+    }
+
+    protected function _getActionsBox(&$aCmt, $aDp = array())
+    {
+    	$bViewOnly = isset($aDp['view_only']) && $aDp['view_only'] === true;
+    	$bDynamicMode = isset($aDp['dynamic_mode']) && $aDp['dynamic_mode'] === true;
+
+        $sMenuActions = '';
+		if(!$bViewOnly) {
+	        $oMenuActions = BxDolMenu::getObjectInstance($this->_sMenuObjActions);
+	        $oMenuActions->setCmtsData($this, $aCmt['cmt_id']);
+	        $oMenuActions->setDynamicMode($bDynamicMode);
+	        $sMenuActions = $oMenuActions->getCode();
+		}
+
+        return $this->_oTemplate->parseHtmlByName('comment_actions.html', array(
+            'id' => $aCmt['cmt_id'],
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'menu_actions' => $sMenuActions,
+        ));
+    }
+
+    protected function _getFormBox($sType, $aBp, $aDp)
+    {
+        $iCmtParentId = isset($aBp['parent_id']) ? (int)$aBp['parent_id'] : 0;
+        $sPosition = isset($aDp['position']) ? $aDp['position'] : '';
+        $bQuote = isset($aDp['quote']) && (bool)$aDp['quote'];
+        $bFormMin = $iCmtParentId == 0 && isset($aDp['min_post_form']) && (bool)$aDp['min_post_form'];
+
+        $sPositionSystem = $this->_aSystem['post_form_position'];
+        if(!empty($sPosition) && $sPositionSystem != $sPosition)
+            return '';
+
+        $sClass = '';
+        if(!empty($sPosition))
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-' . $sPosition;
+        if($bQuote)
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-quote';
+        if($bFormMin)
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-min';
+        if(!empty($aDp['class']))
+            $sClass .= ' ' . $aDp['class'];
+
+        $sClassBody = '';
+        if(!empty($aDp['class_body']))
+            $sClassBody .= ' ' . $aDp['class_body'];
+
+        $aTmplVarsFormMin = array();
+        if($bFormMin) {
+            list($sAuthorName, $sAuthorLink, $sAuthorIcon, $sAuthorUnit) = $this->_getAuthorInfo($this->_getAuthorId());
+
+            $oForm = new BxTemplFormView(array());
+            $aInputPlaceholder = array(
+                'type' => 'text',
+                'name' => 'comment',
+                'caption' => '',
+                'attrs' => array(
+                    'onclick' => 'javascript:' . $this->_sJsObjName . '.cmtShowForm(this)',
+                    'placeholder' => _t($this->_aT['txt_min_form_placeholder'], $sAuthorName)
+                ),
+                'value' => '',
+            );
+
+            $sClassBodyMin = '';
+            if(!empty($aDp['class_body_min']))
+                $sClassBodyMin .= ' ' . $aDp['class_body_min'];
+
+            $aTmplVarsFormMin = array(
+                'js_object' => $this->_sJsObjName,
+                'class_body_min' => $sClassBodyMin,
+                'style_prefix' => $this->_sStylePrefix,
+                'author_unit' => $sAuthorUnit,
+                'placeholder' => $oForm->genRow($aInputPlaceholder)
+            );
+        }
+
+        $sMethod = '_getForm' . ucfirst($sType);
+        $aForm = $this->$sMethod($iCmtParentId, $aDp);
+        if(empty($aForm['form']))
+            return '';
+
+        return $this->_oTemplate->parseHtmlByName('comment_reply_box.html', array(
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => $sClass,
+            'class_body' => $sClassBody,
+            'bx_if:show_form_min' => array(
+                'condition' => $bFormMin,
+                'content' => $aTmplVarsFormMin
+            ),
+            'form' => $aForm['form'],
+            'form_id' => $aForm['form_id'],
+        ));
+    }
+
+    protected function _getFormPost($iCmtParentId = 0, $aDp = array())
+    {
+        $bCmtParentId = !empty($iCmtParentId);
+        if((!$bCmtParentId && !$this->isPostAllowed()) || ($bCmtParentId && !$this->isReplyAllowed($iCmtParentId)))
+            return array('msg' => _t('_Access denied'));
+
+        $bDynamic = isset($aDp['dynamic_mode']) && (bool)$aDp['dynamic_mode'];
+        $bQuote = isset($aDp['quote']) && (bool)$aDp['quote'];
+
+        $oForm = $this->_getForm(BX_CMT_ACTION_POST, $iCmtParentId);
+        $oForm->aInputs['cmt_parent_id']['value'] = $iCmtParentId;
+
+        if($bQuote) {
+            $aCmtParent = $this->getCommentRow((int)$iCmtParentId);
+            if(!empty($aCmtParent['cmt_text']))
+                $oForm->aInputs['cmt_text']['value'] = $this->_oTemplate->parseHtmlByName('comment_quote.html', array(
+                    'content' => $aCmtParent['cmt_text']
+                ));
+        }
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iCmtAuthorId = $this->_getAuthorId();
+            $iCmtParentId = $oForm->getCleanValue('cmt_parent_id');
+            $sCmtText = $oForm->getCleanValue('cmt_text');
+
+            $iLevel = 0;
+            $iCmtVisualParentId = 0;
+            if((int)$iCmtParentId > 0) {
+                $aParent = $this->getCommentRow($iCmtParentId);
+
+                $iLevel = (int)$aParent['cmt_level'] + 1;
+                $iCmtVisualParentId = $iLevel > $this->getMaxLevel() ? $aParent['cmt_vparent_id'] : $iCmtParentId;
+            }
+
+            $iCmtId = (int)$oForm->insert(array('cmt_vparent_id' => $iCmtVisualParentId, 'cmt_object_id' => $this->_iId, 'cmt_author_id' => $iCmtAuthorId, 'cmt_level' => $iLevel, 'cmt_time' => time()));
+            if($iCmtId != 0) {
+                $iCmtUniqId = $this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId, $iCmtAuthorId);
+
+                if($this->isAttachImageEnabled()) {
+                    $aImages = $oForm->getCleanValue('cmt_image');
+                    if(!empty($aImages) && is_array($aImages)) {
+                        $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+
+                        foreach($aImages as $iImageId)
+                            if($this->_oQuery->saveImages($this->_aSystem['system_id'], $iCmtId, $iImageId))
+                                $oStorage->afterUploadCleanup($iImageId, $iCmtAuthorId);
+                    }
+                }
+
+                if($iCmtParentId) {
+                    $this->_oQuery->updateRepliesCount($iCmtParentId, 1);
+
+                    if(!BxDolModuleQuery::getInstance()->isEnabledByName('bx_notifications'))
+                        $this->_sendNotificationEmail($iCmtId, $iCmtParentId);
+                }
+
+                $this->_triggerComment();
+
+                if($bCmtParentId)
+                    $this->isReplyAllowed($iCmtParentId, true);
+                else
+                    $this->isPostAllowed(true);
+
+                if($this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+                    $oMetatags->metaAdd($iCmtUniqId, $sCmtText);
+
+                if(($mixedResult = $this->onPostAfter($iCmtId)) !== false)
+                    return $mixedResult;
+            }
+
+            return array('msg' => _t('_cmt_err_cannot_perform_action'));
+        }
+
+        return array('form' => $oForm->getCode($bDynamic), 'form_id' => $oForm->id);
+    }
+
+    protected function _getFormEdit($iCmtId, $aDp = array())
+    {
+        $bDynamic = isset($aDp['dynamic_mode']) && (bool)$aDp['dynamic_mode'];
+
+        $aCmt = $this->getCommentSimple($iCmtId);
+        if(!$aCmt)
+            return array('msg' => _t('_No such comment'));
+
+        $iCmtAuthorId = $this->_getAuthorId();
+        if(!$this->isEditAllowed($aCmt))
+            return array('msg' => $aCmt['cmt_author_id'] == $iCmtAuthorId ? strip_tags($this->msgErrEditAllowed()) : _t('_Access denied'));
+
+        $oForm = $this->_getForm(BX_CMT_ACTION_EDIT, $aCmt['cmt_id']);
+
+        $oForm->initChecker($aCmt);
+        if($oForm->isSubmittedAndValid()) {
+            $sCmtText = $oForm->getCleanValue('cmt_text');
+
+            if($oForm->update($iCmtId) !== false) {
+                $iCmtUniqId = $this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId, (int)$aCmt['cmt_author_id']);
+
+                $this->isEditAllowed($aCmt, true);
+
+                if($this->_sMetatagsObj && ($oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj)) !== false)
+                    $oMetatags->metaAdd($iCmtUniqId, $sCmtText);
+
+                if(($mixedResult = $this->onEditAfter($iCmtId)) !== false)
+                    return $mixedResult;
+            }
+
+            return array('msg' => _t('_cmt_err_cannot_perform_action'));
+        }
+
+        return array('form' => $oForm->getCode($bDynamic), 'form_id' => $oForm->id);
+    }
+
+    protected function _getForm($sAction, $iId)
+    {
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->setId(sprintf($oForm->getAttributeMask('id'), $sAction, $this->_sSystem, $iId));
+        $oForm->setName(sprintf($oForm->getAttributeMask('name'), $sAction, $this->_sSystem, $iId));
+        $oForm->aParams['db']['table'] = $this->_aSystem['table'];
+        $oForm->aInputs['sys']['value'] = $this->_sSystem;
+        $oForm->aInputs['id']['value'] = $this->_iId;
+        $oForm->aInputs['action']['value'] = 'Submit' . ucfirst($sAction) . 'Form';
+
+        if(!$this->isAttachImageEnabled())
+            unset($oForm->aInputs['cmt_image']);
+
+        if(isset($oForm->aInputs['cmt_text'])) {
+            $oForm->aInputs['cmt_text']['html'] = $this->_aSystem['html'];
+            $oForm->aInputs['cmt_text']['db']['pass'] = $this->isHtml() ? 'XssHtml' : 'XssMultiline';
+
+            $iCmtTextMin = (int)$this->_aSystem['chars_post_min'];
+            $iCmtTextMax = (int)$this->_aSystem['chars_post_max'];
+            $oForm->aInputs['cmt_text']['checker']['params'] = array($iCmtTextMin, $iCmtTextMax);
+            $oForm->aInputs['cmt_text']['checker']['error'] = _t('_Please enter n1-n2 characters', $iCmtTextMin, $iCmtTextMax);
+        }
+
+        return $oForm;
+    }
+
+    protected function _getMoreLink($sCmts, $aBp = array(), $aDp = array())
+    {
+        $iStart = $iPerView = 0;
+        switch($aBp['type']) {
+            case BX_CMT_BROWSE_HEAD:
+            case BX_CMT_BROWSE_POPULAR:
+            case BX_CMT_BROWSE_CONNECTION:
+                $iPerView = $aBp['per_view'];
+
+                $iStart = $aBp['start'] + $iPerView;
+                if($iStart >= $aBp['count'])
+                    return $sCmts;
+
+                break;
+
+            case BX_CMT_BROWSE_TAIL:
+                $iPerView = $aBp['per_view'];
+
+                $iStart = $aBp['start'] - $iPerView;
+                if($iStart < 0) {
+                    $iPerView += $iStart;
+                    $iStart = 0;
+                }
+
+                if($iStart == 0 && $iPerView == 0)
+                    return $sCmts;
+
+                break;
+        }
+
+        $bRoot = (int)$aBp['vparent_id'] <= 0;
+
+        $sMore = $this->_oTemplate->parseHtmlByName('comment_more.html', array(
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:is_root' => array(
+                'condition' => $bRoot,
+                'content' => array()
+            ),
+            'parent_id' => $aBp['vparent_id'],
+            'start' => $iStart,
+            'per_view' => $iPerView,
+            'title' => _t('_cmt_load_more_' . ($aBp['vparent_id'] == 0 ? 'comments' : 'replies') . '_' . $aBp['type'])
+        ));
+
+        switch($aBp['type']) {
+            case BX_CMT_BROWSE_HEAD:
+                case BX_CMT_BROWSE_POPULAR:
+                case BX_CMT_BROWSE_CONNECTION:
+                $sCmts .= $sMore;
+                break;
+
+            case BX_CMT_BROWSE_TAIL:
+                $sCmts = $sMore . $sCmts;
+                break;
+        }
+
+        return $sCmts;
+    }
+
+    protected function _getEmpty($aDp = array())
+    {
+        $sClass = '';
+        if(!empty($aDp['class']))
+            $sClass .= ' ' . $aDp['class'];
+
+        return $this->_oTemplate->parseHtmlByName('comment_empty.html', array(
+            'style_prefix' => $this->_sStylePrefix,
+            'class' => $sClass,
+            'content' => MsgBox(_t('_Empty'))
+        ));
+    }
+
+    protected function _getViewImagePopup()
+    {
+    	$sViewImagePopupId = 'cmts-box-' . $this->_sSystem . '-' . $this->getId() . '-view-image-popup' ;
+        $sViewImagePopupContent = $this->_oTemplate->parseHtmlByName('popup_image.html', array(
+    		'image_url' => ''
+    	));
+
+    	return BxTemplFunctions::getInstance()->transBox($sViewImagePopupId, $sViewImagePopupContent, true);
+    }
+
+    protected function _getAttachments($aCmt)
+    {
+        $aTmplImages = array();
+        if(!$this->isAttachImageEnabled())
+            return ''; 
+
+        $aFiles = $this->_oQuery->getFiles($this->_aSystem['system_id'], $aCmt['cmt_id']);
+        if(!empty($aFiles) && is_array($aFiles)) {
+    		$oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance($this->getTranscoderPreviewName());
+
+            foreach($aFiles as $aFile) {
+            	$bImage = $oTranscoder && $oTranscoder->isMimeTypeSupported($aFile['mime_type']);
+
+            	$sPreview = '';
+            	if($oTranscoder && $bImage)
+            		$sPreview = $oTranscoder->getFileUrl($aFile['image_id']);
+
+        		if(!$sPreview)
+            		$sPreview = $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
+
+				$aTmplVarsFile = array(
+				    'js_object' => $this->_sJsObjName,
+					'preview' => $sPreview,
+					'file' => $oStorage->getFileUrlById($aFile['image_id']),
+        			'file_name' => $aFile['file_name'],
+                    'file_icon' => $oStorage->getFontIconNameByFileName($aFile['file_name']),
+				    'file_size' => _t_format_size($aFile['size']),
+				);
+
+                $aTmplImages[] = array(
+                    'style_prefix' => $this->_sStylePrefix,
+                	'bx_if:show_image' => array(
+                		'condition' => $bImage,
+                		'content' => $aTmplVarsFile
+                	),
+                    'bx_if:show_file' => array(
+                		'condition' => !$bImage,
+                		'content' => $aTmplVarsFile
+                	),
+                );
+            }
+        }
+
+        return $this->_oTemplate->parseHtmlByName('comment_attachments.html', array(
+            'bx_repeat:attached' => $aTmplImages
+        ));
+    }
+
+    protected function _getTmplVarsAuthor($aCmt)
+    {
+    	list($sAuthorName, $sAuthorLink, $sAuthorIcon, $sAuthorUnit) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+    	$bAuthorIcon = !empty($sAuthorIcon);
+
+    	return array(
+    	    'author_unit' => $sAuthorUnit,
+            'bx_if:show_icon' => array(
+                'condition' => $bAuthorIcon,
+                'content' => array(
+                    'author_icon' => $sAuthorIcon
+                )
+            ),
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bAuthorIcon,
+                'content' => array()
+            ),
+            'bx_if:show_author_link' => array(
+                'condition' => !empty($sAuthorLink),
+                'content' => array(
+                    'author_link' => $sAuthorLink,
+            		'author_title' => bx_html_attribute($sAuthorName),
+                    'author_name' => $sAuthorName
+                )
+            ),
+            'bx_if:show_author_text' => array(
+                'condition' => empty($sAuthorLink),
+                'content' => array(
+                    'author_name' => $sAuthorName
+                )
+            ),
+    	);
+    }
+
+    protected function _getTmplVarsText($aCmt)
+    {
+    	$sText = $aCmt['cmt_text'];
+        $sTextMore = '';
+
+        if(!$this->isHtml()) {
+            $iMaxLength = (int)$this->_aSystem['chars_display_max'];
+            if(strlen($sText) > $iMaxLength) {
+                $iLength = strpos($sText, ' ', $iMaxLength);
+    
+                $sTextMore = trim(substr($sText, $iLength));
+                $sText = trim(substr($sText, 0, $iLength));
+            }
+        }
+
+        $sText = $this->_prepareTextForOutput($sText, $aCmt['cmt_id']);
+        $sTextMore = $this->_prepareTextForOutput($sTextMore, $aCmt['cmt_id']);
+
+        return array(
+			'text' => $sText,
+            'bx_if:show_more' => array(
+                'condition' => !empty($sTextMore),
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'js_object' => $this->_sJsObjName,
+                    'text_more' => $sTextMore
+                )
+            ),
+        );
+    }
+}
+
+/** @} */

+ 169 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseCmtsMenuActions.php

@@ -0,0 +1,169 @@
+<?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
+ * @{
+ */
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseCmtsMenuActions extends BxTemplMenuCustom
+{
+    protected $_oCmts;
+    protected $_aCmt;
+
+    protected $_bShowTitles;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+
+        $this->_bShowTitles = false;
+    }
+
+    public function setCmtsData($oCmts, $iCmtId)
+    {
+        if(empty($oCmts) || empty($iCmtId))
+            return;
+
+        $this->_oCmts = $oCmts;
+        $this->_aCmt = $oCmts->getCommentRow($iCmtId);
+
+        $sJsObject = $oCmts->getJsObjectName();
+        $this->addMarkers(array(
+            'js_object' => $sJsObject,
+            'cmt_system' => $this->_oCmts->getSystemName(),
+            'cmt_object_id' => $this->_oCmts->getId(),
+            'cmt_id' => $iCmtId,
+            'content_id' => $iCmtId,
+            'reply_onclick' => $sJsObject . '.toggleReply(this, ' . $iCmtId . ')',
+            'quote_onclick' => $sJsObject . '.toggleQuote(this, ' . $iCmtId . ')'
+        ));
+    }
+
+    protected function _getMenuItemItemVote($aItem)
+    {
+        $oVote = $this->_oCmts->getVoteObject($this->_aCmt['cmt_unique_id']);
+        if(!$oVote)
+            return false;
+
+        $aVotesParams = array('dynamic_mode' => $this->_bDynamicMode);
+        if($this->_bShowTitles)
+            $aVotesParams['show_do_vote_label'] = true;
+
+        return $oVote->getElementInline($aVotesParams);
+    }
+
+    protected function _getMenuItemItemReaction($aItem)
+    {
+        $oReaction = $this->_oCmts->getReactionObject($this->_aCmt['cmt_unique_id']);
+        if(!$oReaction)
+            return false;
+
+        $aReactionParams = array('dynamic_mode' => $this->_bDynamicMode);
+        if($this->_bShowTitles)
+            $aReactionParams['show_do_vote_label'] = true;
+
+        return $oReaction->getElementInline($aReactionParams);
+    }
+
+    protected function _getMenuItemItemScore($aItem)
+    {
+        $oScore = $this->_oCmts->getScoreObject($this->_aCmt['cmt_unique_id']);
+        if(!$oScore)
+        	return false;
+
+        $aScoresParams = array('dynamic_mode' => $this->_bDynamicMode);
+        if($this->_bShowTitles)
+		    $aScoresParams['show_do_vote_label'] = true;
+
+    	return $oScore->getElementInline($aScoresParams);
+    }
+
+	protected function _getMenuItemItemReport($aItem)
+    {
+        $oReport = $this->_oCmts->getReportObject($this->_aCmt['cmt_unique_id']);
+        if(!$oReport)
+        	return false;
+
+        $aReportParams = array('dynamic_mode' => $this->_bDynamicMode);
+        if($this->_bShowTitles)
+		    $aReportParams['show_do_report_label'] = true;
+
+    	return $oReport->getElementInline($aReportParams);
+    }
+
+    /**
+     * Check if menu items is visible.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        $sCheckFuncName = '';
+        $aCheckFuncParams = array();
+        switch ($a['name']) {
+            case 'item-reply':
+                $sCheckFuncName = 'isReplyAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-quote':
+                $sCheckFuncName = 'isQuoteAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-vote':
+                $sCheckFuncName = 'isVoteAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-score':
+                $sCheckFuncName = 'isScoreAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-report':
+                $sCheckFuncName = 'isReportAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-edit':
+                $sCheckFuncName = 'isEditAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-delete':
+                $sCheckFuncName = 'isRemoveAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+
+            case 'item-more':
+                $sCheckFuncName = 'isMoreAllowed';
+                if(!empty($this->_aCmt))
+                    $aCheckFuncParams = array($this->_aCmt);
+                break;
+        }
+
+        if(!$sCheckFuncName || !method_exists($this->_oCmts, $sCheckFuncName))
+            return true;
+
+        return call_user_func_array(array($this->_oCmts, $sCheckFuncName), $aCheckFuncParams);
+    }
+}
+
+/** @} */

+ 1769 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseFormView.php

@@ -0,0 +1,1769 @@
+<?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 BxBaseFormView extends BxDolForm
+{
+    protected static $_isToggleJsAdded = false;
+
+    protected static $_isCssJsAdded = false;
+    protected static $_isCssJsUiAdded = false;
+    protected static $_isCssJsMinicolorsAdded = false;
+    protected static $_isCssJsTimepickerAdded = false;
+    protected static $_isCssJsAddedViewMode = false;
+
+    /**
+     * Enable or disable error message displaying
+     */
+    protected $bEnableErrorIcon = true;
+
+    /**
+     * HTML Code of this form
+     */
+    protected $sCode;
+
+    /**
+     * Code which will be added to the beginning of the form.
+     * For example, hidden inputs.
+     * For internal use only
+     */
+    protected $_sCodeAdd = '';
+
+    /**
+     * for internal use only
+     */
+    protected $_isSectionOpened = false;
+
+    /**
+     * Default divider for several inputs
+     */
+    protected $_sDivider = '<span class="bx-def-margin-left"></span>';
+
+    /**
+     * Alternative divider for several inputs
+     */
+    protected $_sDividerAlt = '<br />';
+
+    /**
+     * Form is added dynamically.
+     */
+    protected $_bDynamicMode = false;
+
+    /**
+     * Form is submitted dynamically (using Ajax Submit).
+     */
+    protected $_bAjaxMode = false;
+    
+    /**
+     * Use absolute Action URL which is needed in Ajax Mode.
+     */
+    protected $_bAbsoluteActionUrl = false;
+
+    /**
+     * Form is displayed in view mode.
+     */
+    protected $_bViewMode = false;
+
+    /**
+     * Function name for generation close form section HTML.
+     */
+    protected $_sSectionClose = 'getCloseSection';
+
+    /**
+     * Function name for generation open form section HTML.
+     */
+    protected $_sSectionOpen = 'getOpenSection';
+
+    /**
+     * JS files list for form
+     */
+    protected $_aJs = array();
+
+    /**
+     * CSS files list for form
+     */
+    protected $_aCss = array();
+    
+    /**
+     * Constructor
+     *
+     * @param array $aInfo Form contents
+     *
+     * $aInfo['params'] = array(
+     *     'remove_form' => true|false,
+     * );
+     *
+     * @return BxBaseFormView
+     */
+    function __construct($aInfo, $oTemplate)
+    {
+        parent::__construct($aInfo, $oTemplate);
+
+        $this->_bAjaxMode = isset($this->aParams['ajax_mode']) && $this->aParams['ajax_mode'];
+        $this->_bViewMode = isset($this->aParams['view_mode']) && $this->aParams['view_mode'];
+
+        if($this->_bViewMode) {
+            $this->_sSectionClose = 'getCloseSectionViewMode';
+            $this->_sSectionOpen = 'getOpenSectionViewMode';
+        }
+    }
+
+    function setAjaxMode($bAjaxMode)
+    {
+        $this->_bAjaxMode = (bool)$bAjaxMode;
+    }
+
+    function isAjaxMode()
+    {
+        return $this->_bAjaxMode;
+    }
+
+    function setAbsoluteActionUrl($sUrl)
+    {
+        if(empty($sUrl))
+            return;
+
+        $this->aFormAttrs['action'] = $sUrl;
+
+        $this->_bAbsoluteActionUrl = true;
+    }
+
+    /**
+     * Return Form code
+     * @param $bDynamicMode - set it to true if form is added via JS/AJAX call, for example form in AJAX popup.
+     * @return string
+     */
+    function getCode($bDynamicMode = false)
+    {
+        if (!$bDynamicMode && bx_is_dynamic_request())
+            $bDynamicMode = true;
+
+        $this->_bDynamicMode = $bDynamicMode;
+        $this->aFormAttrs = $this->_replaceMarkers($this->aFormAttrs);
+        $this->sCode = $this->genForm();
+
+        $this->addCssJs ();
+        $sDynamicCssJs = $this->_processCssJs();
+        return $sDynamicCssJs . $this->sCode;
+    }
+
+    /**
+     * Generate the whole form
+     *
+     * @return string
+     */
+    function genForm()
+    {
+        $this->_sCodeAdd = '';
+
+        $sTable = $this->genRows();
+
+        $sHtmlBefore = isset($this->aParams['html_before']) ? $this->aParams['html_before'] : '';
+        $sHtmlAfter = isset($this->aParams['html_after']) ? $this->aParams['html_after'] : '';
+
+        if (!empty($this->aParams['remove_form']) || (isset($this->aParams['view_mode']) && $this->aParams['view_mode'])) {
+            $sForm = <<<BLAH
+                    $sHtmlBefore
+                    {$this->_sCodeAdd}
+                    <div class="bx-form-advanced-wrapper {$this->id}_wrapper">
+                        $sTable
+                    </div>
+                    $sHtmlAfter
+BLAH;
+        } else {
+            $sFormAttrs = bx_convert_array2attrs($this->aFormAttrs, 'bx-form-advanced');
+
+            $sAjaxFormJs = '';
+            if($this->_bAjaxMode)
+            	$sAjaxFormJs = <<<BLAH
+	            	$("form#{$this->id}").ajaxForm({ 
+			            dataType: "json",
+			            beforeSubmit: function (formData, jqForm, options) {
+			                bx_loading($("form#{$this->id}"), true);
+			            },
+			            success: function (oData) {
+			            	bx_loading($("form#{$this->id}"), false);
+			
+			                processJsonData(oData);
+			            }
+			        });
+BLAH;
+
+            $sForm = <<<BLAH
+                $sHtmlBefore
+                <form $sFormAttrs>
+                    {$this->_sCodeAdd}
+                    <div class="bx-form-advanced-wrapper {$this->id}_wrapper">
+                        $sTable
+                    </div>
+                </form>
+                <script>
+                    $(document).ready(function() {
+                        $(this).addWebForms();
+                    });
+                    $sAjaxFormJs
+                </script>
+                $sHtmlAfter
+BLAH;
+        }
+
+        return $sForm;
+    }
+
+    /**
+     * Generate Table HTML code
+     *
+     * @return string
+     */
+    function genRows()
+    {
+        // add CSRF token if it's needed.
+        if (!(isset($this->aParams['view_mode']) && $this->aParams['view_mode']) && getParam('sys_security_form_token_enable') == 'on' && (!isset($this->aParams['csrf']['disable']) || (isset($this->aParams['csrf']['disable']) && $this->aParams['csrf']['disable'] !== true)) && ($mixedCsrfToken = BxDolForm::getCsrfToken()) !== false) {
+            $this->aInputs['csrf_token'] = array(
+                'type' => 'hidden',
+                'name' => 'csrf_token',
+                'value' => $mixedCsrfToken,
+                'db' => array ('pass' => 'Xss'),
+                'visible_for_levels' => PHP_INT_MAX,
+            );
+        }
+
+        // add 'Ajax Mode' flag
+        if($this->_bAjaxMode)
+            $this->aInputs['ajax_mode'] = array(
+                'type' => 'hidden',
+                'name' => 'ajax_mode',
+                'value' => 1,
+                'db' => array ('pass' => 'Int'),
+                'visible_for_levels' => PHP_INT_MAX,
+            );
+
+        // add 'Absolute Action Url' flag
+        if($this->_bAbsoluteActionUrl)
+            $this->aInputs['absolute_action_url'] = array(
+                'type' => 'hidden',
+                'name' => 'absolute_action_url',
+                'value' => 1,
+                'db' => array ('pass' => 'Int'),
+                'visible_for_levels' => PHP_INT_MAX,
+            );
+
+        // check if we need to generate open section clause
+        $sOpenSection = '';
+        foreach ($this->aInputs as $aInput) {
+            if (isset($aInput['type']) && 'hidden' == $aInput['type'])
+                continue;
+            if (isset($aInput['type']) && 'block_header' != $aInput['type'])
+                $sOpenSection = $this->{$this->_sSectionOpen}();
+            break;
+        }
+
+        // generate rows contents
+        $sCont = '';
+        $sContHeader = '';
+        $sContFields = '';
+        $sFuncGenRow = isset($this->aParams['view_mode']) && $this->aParams['view_mode'] ? 'genViewRow' : 'genRow';
+        foreach ($this->aInputs as $aInput) {
+            if (!isset($aInput['visible_for_levels']) || self::isVisible($aInput)) {
+                if ('block_header' == $aInput['type']) {
+                    // don't show section with no fields or with empty fields
+                    if ($sContHeader) {                        
+                        if ($sContFields)
+                            $sCont .= $sContHeader . $sContFields;
+                        else
+                            $sContHeader = '';
+                        $sContFields = '';
+                    } 
+                    else {
+                        $sCont .= $sContFields;
+                        $sContFields = '';
+                    }
+                    $sContHeader = $this->$sFuncGenRow($aInput);
+                } else {
+                    $sContFields .= $this->$sFuncGenRow($aInput);
+                }
+            }
+        }
+        $sCont .= $sContHeader . $sContFields;
+
+        $sCloseSection = $this->{$this->_sSectionClose}();
+
+        return $sOpenSection . $sCont . $sCloseSection;
+    }
+
+    /**
+     * Generate single Table Row
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genRow(&$aInput)
+    {
+        if (!isset($aInput['type']))
+            $aInput['type'] = false;
+
+        if (!empty($aInput['name'])) {
+            $sCustomMethod = 'genCustomRow' . $this->_genMethodName($aInput['name']);
+            if (method_exists($this, $sCustomMethod))
+                return $this->$sCustomMethod($aInput);
+        }
+
+        switch ($aInput['type']) {
+
+            case 'block_header':
+                $sRow = $this->genRowBlockHeader($aInput);
+            break;
+
+            case 'block_end':
+                $sRow = $this->genBlockEnd($aInput);
+            break;
+
+            case 'hidden':
+                // do not generate row for hidden inputs
+                $sRow = '';
+                $this->_sCodeAdd .= $this->genInput($aInput);
+            break;
+
+            case 'select_box':
+                $sRow = $this->genRowCustom($aInput, 'genInputSelectBox');
+            break;
+
+            case 'files':
+                $sRow = $this->genRowCustom($aInput, 'genInputFiles');
+            break;
+
+            case 'switcher':
+            case 'checkbox':
+                $sRow = $this->genRowStandard($aInput, true);
+            break;
+
+            default:
+                $sRow = $this->genRowStandard($aInput);
+        }
+
+        return $sRow;
+    }
+
+    /**
+     * Generate single Table Row for view mode
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genViewRow(&$aInput)
+    {
+        if (!isset($aInput['type']))
+            $aInput['type'] = false;
+
+        if (!empty($aInput['name'])) {
+            $sCustomMethod = 'genCustomRow' . $this->_genMethodName($aInput['name']);
+            if (method_exists($this, $sCustomMethod))
+                return $this->$sCustomMethod($aInput);
+        }
+
+        switch ($aInput['type']) {
+
+            case 'block_header':
+                $sRow = $this->genRowBlockHeader($aInput);
+            break;
+
+            case 'block_end':
+                $sRow = $this->genBlockEnd($aInput);
+            break;
+
+            default:
+                $sRow = $this->genViewRowWrapped($aInput);
+        }
+
+        return $sRow;
+    }
+
+    /**
+     * Generate complete wrapped row for view mode form
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genViewRowWrapped(&$aInput)
+    {
+        $sValue = $this->genViewRowValue($aInput);
+        if (null === $sValue)
+            return '';
+
+        $sCaption = isset($aInput['caption']) ? bx_process_output($aInput['caption']) : '';
+
+        return <<<EOS
+            <div class="bx-form-row-view-wrapper bx-form-row-view-wrapper-{$aInput['type']} bx-def-padding-sec-top">
+                <div class="bx-form-row-view-caption">{$sCaption}:</div>
+                <div class="bx-form-row-view-value">{$sValue}</div>
+            </div>
+EOS;
+    }
+
+    /**
+     * Generate value for view mode row
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genViewRowValue(&$aInput)
+    {
+        if (!empty($aInput['name'])) {
+            $sCustomMethod = 'genCustomViewRowValue' . $this->_genMethodName($aInput['name']);
+            if (method_exists($this, $sCustomMethod))
+                return $this->$sCustomMethod($aInput);
+        }
+
+        switch ($aInput['type']) {
+
+            case 'hidden':
+                $sValue = null;
+            break;
+
+            case 'select':
+            case 'radio_set':
+                $sValue = $this->genViewRowValueForSelect($aInput);
+            break;
+
+            case 'datepicker':
+                $sValue = null;
+                if (empty($aInput['value']) || !$aInput['value'] || '0000-00-00' == $aInput['value'])
+                    break;
+
+                $sValue = BxTemplFunctions::getInstance()->{is_numeric($aInput['value']) ? 'timeForJs' : 'timeForJsFullDate'}($aInput['value'], isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE, true);
+            break;
+
+            case 'date_time':
+            case 'datetime':
+                $sValue = null;
+                if(empty($aInput['value']) || !$aInput['value'] || '0000-00-00 00:00:00' == $aInput['value'] || '0000-00-00 00:00' == $aInput['value'])
+                    break;
+
+                $sValue = BxTemplFunctions::getInstance()->{is_numeric($aInput['value']) ? 'timeForJs' : 'timeForJsFullDate'}($aInput['value'], isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE_TIME, true);
+            break;
+
+            case 'checkbox_set':
+            case 'select_multiple':
+                $sValue = null;
+                if (!empty($aInput['value']) && is_array($aInput['value'])) {
+                    $sValue = '';
+                    foreach ($aInput['value'] as $sVal)
+                        $sValue .= $aInput['values'][$sVal] . ', ';
+                    $sValue = trim ($sValue, ', ');
+                }
+            break;
+
+            case 'checkbox':
+            case 'switcher':
+                $sValue = isset($aInput['checked']) && $aInput['checked'] ? _t('_sys_form_checkbox_value_on') : _t('_sys_form_checkbox_value_off');
+            break;
+
+            case 'textarea':
+                if (isset($aInput['value']) && '' !== $aInput['value']) 
+                    $sValue = (isset($aInput['html']) && $aInput['html']) || (isset($aInput['code']) && $aInput['code']) ? $aInput['value'] : bx_process_output($aInput['value'], BX_DATA_TEXT_MULTILINE);
+                else
+                    $sValue = null;
+            break;
+
+            case 'text':
+                if(isset($aInput['value']) && '' !== $aInput['value'])
+                    $sValue = bx_linkify(bx_process_output($aInput['value']));
+                else 
+                    $sValue = null;
+                break;
+
+            default:
+                $sValue = isset($aInput['value']) && '' !== $aInput['value'] ? bx_process_output($aInput['value']) : null;
+        }
+
+        return $sValue;
+    }
+
+    function genViewRowValueForSelect(&$aInput)
+    {
+        if (!isset($aInput['value']) || !$aInput['value'])
+            return null;
+        $s = isset($aInput['value']) && isset($aInput['values'][$aInput['value']]) ? $aInput['values'][$aInput['value']] : null;
+        if (isset($aInput['values_list_name'])  && ($oCategory = BxDolCategory::getObjectInstanceByFormAndList($this->aFormAttrs['name'], $aInput['values_list_name'])))
+            return $oCategory->getCategoryLink($s, $aInput['value']);
+        return $s;
+    }
+
+    protected function genCustomRowBirthday(&$aInput)
+    {
+        if(!$this->_bViewMode)
+            return $this->genRowStandard($aInput);
+
+        $aInput['caption_src'] = '_sys_form_input_age';
+        $aInput['caption'] = _t($aInput['caption_src']);
+        return $this->genViewRowWrapped($aInput);
+    }
+
+    protected function genCustomViewRowValueBirthday(&$aInput)
+    {
+        if(!isset($aInput['value']) || !$aInput['value'] || in_array($aInput['value'], array('0000-00-00', '0000-00-00 00:00:00')))
+            return null;
+
+        $sValue = $aInput['value'];
+
+        $iPosSpace = strpos($sValue, ' ');
+        if($iPosSpace !== false)
+            $sValue = trim(substr($sValue, 0, $iPosSpace));
+
+        $aDate = explode('-', $sValue);
+
+        $iCdYear = (int)date('Y');
+        $iCdMonth = (int)date('n');
+        $iCdDay = (int)date('j');
+
+        $iResult = $iCdYear - (int)$aDate[0];
+        if($iCdMonth < (int)$aDate[1] || ($iCdMonth == (int)$aDate[1] && $iCdDay < (int)$aDate[2]))
+            $iResult -= 1;           
+
+        return $iResult;
+    }
+
+    /**
+     * Generate standard row
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genRowStandard(&$aInput, $isOneLine = false)
+    {
+        $sCaption = isset($aInput['caption']) ? bx_process_output($aInput['caption'], BX_DATA_HTML) : '';
+
+        $sRequired = !empty($aInput['required']) ? '<span class="bx-form-required">*</span> ' : '';
+
+        $sClassAdd = !empty($aInput['error']) ? ' bx-form-error' : '';
+        $sInfoIcon = !empty($aInput['info']) ? $this->genInfoIcon($aInput['info']) : '';
+
+        $sErrorIcon = $this->genErrorIcon(empty($aInput['error']) ? '' : $aInput['error']);
+
+        $sClassWrapper = 'bx-form-element-wrapper';
+        if($isOneLine)
+            $sClassWrapper .= ' ' . $sClassWrapper . '-oneline';
+        $sClassWrapper .= ' bx-def-margin-top';
+
+        if (isset($aInput['name']))
+            $aInput['tr_attrs']['id'] = "bx-form-element-" . $aInput['name'];
+        $sTrAttrs = bx_convert_array2attrs(isset($aInput['tr_attrs']) && is_array($aInput['tr_attrs']) ? $aInput['tr_attrs'] : array(), $sClassWrapper);
+
+        $sClassOneLineCaption = '';
+        $sClassOneLineValue = '';
+        if ($isOneLine) {
+            $sClassOneLineCaption = ' bx-form-caption-oneline bx-form-caption-oneline-' . $aInput['type'] . ' bx-def-margin-sec-left';
+            $sClassOneLineValue = ' bx-form-value-oneline bx-form-value-oneline-' . $aInput['type'];
+            $aInput['attrs']['id'] = $this->getInputId($aInput);
+            if ($sCaption)
+                $sCaption = '<label for="' . $aInput['attrs']['id'] . '">' . $sCaption . '</label>';
+        }
+
+        $sInput = $this->genInput($aInput);
+        if(isset($aInput['error_updated']) && $aInput['error_updated'] === true)
+            $sErrorIcon = $this->genErrorIcon(empty($aInput['error']) ? '' : $aInput['error']);
+
+        $sCaptionCode = $sCaption ? '<div class="bx-form-caption' . $sClassOneLineCaption . '">' . $sCaption . $sRequired . '</div>' : '';
+        $sInputCode = $this->genWrapperInput($aInput, $sInput);
+
+        if (empty($sInfoIcon)) $sInfoIcon = '';
+        if (empty($sInputCode)) $sInputCode = '';
+        if (empty($sErrorIcon)) $sErrorIcon = '';
+
+        $sValueCode = '<div class="bx-form-value bx-clearfix' . $sClassAdd . $sClassOneLineValue . '">' . $sInputCode . '</div>';
+
+        if ($isOneLine)
+            $sCode = $sValueCode . $sCaptionCode;
+        else
+            $sCode = $sCaptionCode . $sValueCode;
+
+        return '<div ' . $sTrAttrs . '><div class="bx-form-element">' . $sCode . '</div>' . $sInfoIcon . $sErrorIcon . '</div>';
+    }
+
+
+    function genWrapperInput($aInput, $sContent)
+    {
+        $sAttr = isset($aInput['attrs_wrapper']) && is_array($aInput['attrs_wrapper']) ? bx_convert_array2attrs($aInput['attrs_wrapper']) : '';
+
+        return "<div class=\"bx-form-input-wrapper bx-form-input-wrapper-{$aInput['type']}" . ((isset($aInput['html']) && $aInput['html'] && $this->isHtmlEditor($aInput['html'], $aInput)) ? ' bx-form-input-wrapper-html' : '') . "\" $sAttr>$sContent</div>";
+    }
+
+    /**
+     * Generate custom row
+     *
+     * @param  array  $aInput
+     * @param  string $sCustomMethod custom method to generate code for input
+     * @return string
+     */
+    function genRowCustom(&$aInput, $sCustomMethod)
+    {
+        $sCaption = isset($aInput['caption']) ? bx_process_output($aInput['caption']) : '';
+
+        $sRequired = !empty($aInput['required']) ? '<span class="bx-form-required">*</span> ' : '';
+
+        $sClassAdd = !empty($aInput['error']) ? ' bx-form-error' : '';
+        $sInfoIcon = !empty($aInput['info']) ? $this->genInfoIcon($aInput['info']) : '';
+
+        $sErrorIcon = $this->genErrorIcon(empty($aInput['error']) ? '' : $aInput['error']);
+        $sInput = $this->$sCustomMethod($aInput, $sInfoIcon, $sErrorIcon);
+
+        if (isset($aInput['name']))
+            $aInput['tr_attrs']['id'] = "bx-form-element-" . $aInput['name'];
+        $sTrAttrs = bx_convert_array2attrs(empty($aInput['tr_attrs']) ? array() : $aInput['tr_attrs'], "bx-form-element-wrapper bx-def-margin-top");
+
+        $sCaptionCode = '';
+        if ($sCaption)
+            $sCaptionCode = '<div class="bx-form-caption">' . $sCaption . $sRequired . '</div>';
+
+        $sCode = <<<BLAH
+                <div $sTrAttrs>
+                    $sCaptionCode
+                    <div class="bx-form-value$sClassAdd">
+                        <div class="bx-clear"></div>
+                            $sInput
+                        <div class="bx-clear"></div>
+                    </div>
+                </div>
+BLAH;
+
+        return $sCode;
+    }
+
+    /**
+     * Generate Block Headers row
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genRowBlockHeader(&$aInput)
+    {
+        $aAttrs = empty($aInput['attrs']) ? '' : $aInput['attrs'];
+
+        // if there is no caption - show divider only
+
+        if (empty($aInput['caption'])) {
+            $sCode = $this->{$this->_sSectionClose}();
+            $sCode .= $this->{$this->_sSectionOpen}($aAttrs);
+            return $sCode;
+        }
+
+        // if section is collapsed by default, add necessary code
+
+        $sClassAddCollapsable = 'bx-form-collapsable';
+        if (isset($aInput['collapsed']) and $aInput['collapsed'])
+            $sClassAddCollapsable .= ' bx-form-collapsed bx-form-section-hidden';
+
+        // display section with caption
+
+        $sCode = $this->{$this->_sSectionClose}();
+
+        if (empty($aAttrs))
+            $aAttrs = array('class' => 'bx-form-collapsable ' . $sClassAddCollapsable);
+        else
+            $aAttrs['class'] .= ' bx-form-collapsable ' . $sClassAddCollapsable;
+
+
+        if (isset($this->aParams['view_mode']) && $this->aParams['view_mode'])
+            $sLegend = '<legend class="bx-def-padding-left bx-def-padding-sec-right bx-def-font-grayed bx-def-font-h3">' . bx_process_output($aInput['caption']) . (!empty($aInput['info']) ? '<br /><span>' . bx_process_output($aInput['info']) . '</span>' : '') . '</legend>';
+        else
+            $sLegend = '<legend class="bx-def-padding-left bx-def-padding-sec-right bx-def-font-grayed bx-def-font-h3"><a href="javascript:void(0);">' . bx_process_output($aInput['caption']) . '</a>' . (!empty($aInput['info']) ? '<br /><span>' . bx_process_output($aInput['info']) . '</span>' : '') . '</legend>';
+
+        $sCode .= $this->{$this->_sSectionOpen}($aAttrs, $sLegend);
+
+        return $sCode;
+    }
+
+    function genBlockEnd()
+    {
+        $aNextTbodyAdd = false; // need to have some default
+        $sCode = '';
+        $sCode .= $this->{$this->_sSectionClose}();
+        $sCode .= $this->{$this->_sSectionOpen}($aNextTbodyAdd);
+        return $sCode;
+    }
+
+    /**
+     * Generate HTML Input Element
+     *
+     * @param  array  $aInput
+     * @return string Output HTML Code
+     */
+    function genInput(&$aInput)
+    {
+        if (!empty($aInput['name'])) {
+            $sCustomMethod = 'genCustomInput' . $this->_genMethodName($aInput['name']);
+            if (method_exists($this, $sCustomMethod))
+                return $this->$sCustomMethod($aInput);
+        }
+
+        switch ($aInput['type']) {
+
+            // standard inputs (and non-standard, interpreted as standard)
+            case 'text':
+            case 'datepicker':
+            case 'date_time':
+            case 'datetime':
+            case 'number':
+            case 'time':
+            case 'checkbox':
+            case 'radio':
+            case 'image':
+            case 'password':
+            case 'slider':
+            case 'doublerange':
+            case 'hidden':
+                $sInput = $this->genInputStandard($aInput);
+            break;
+
+            case 'file':
+            	$sInput = $this->genInputFile($aInput);
+            	break;
+
+            case 'rgb':
+            case 'rgba':
+            	$sClass = 'bx-form-input-' . $aInput['type'];
+            	$aInput['attrs']['class'] = !empty($aInput['attrs']['class']) ? $aInput['attrs']['class'] . ' ' . $sClass : $sClass; 
+
+            	$aInput['type'] = 'text';
+                $sInput = $this->genInputStandard($aInput);
+            break;
+
+            case 'switcher':
+                $sInput = $this->genInputSwitcher($aInput);
+            break;
+
+            case 'button':
+            case 'reset':
+            case 'submit':
+                $sInput = $this->genInputButton($aInput);
+            break;
+
+            case 'textarea':
+                $sInput = $this->genInputTextarea($aInput);
+            break;
+
+            case 'select':
+                $sInput = $this->genInputSelect($aInput);
+            break;
+
+            case 'select_multiple':
+                $sInput = $this->genInputSelectMultiple($aInput);
+            break;
+
+            case 'checkbox_set':
+                $sInput = $this->genInputCheckboxSet($aInput);
+            break;
+
+            case 'radio_set':
+                $sInput = $this->genInputRadioSet($aInput);
+            break;
+
+            case 'input_set': // numeric array of inputs
+                $sInput = '';
+                $sDivider = isset($aInput['dv']) ? $aInput['dv'] : ' ';
+                foreach ($aInput as $iKey => $aSubInput) {
+                    if (!is_int($iKey) or !$aSubInput)
+                        continue; // parse only integer keys and existing values
+
+                    $sInput .= $this->genInput($aSubInput); // recursive call
+                    $sInput .= $sDivider;
+                }
+                $sInput .= '<div class="bx-clear"></div>';
+            break;
+
+            case 'custom':
+                $sInput = isset($aInput['content']) ? $aInput['content'] : '';
+            break;
+
+            case 'captcha':
+                $sInput = $this->genInputCaptcha($aInput);
+            break;
+
+            case 'location':
+                $sInput = $this->genInputLocation($aInput);
+            break;
+
+            case 'value':
+                $sInput = isset($aInput['value']) ? $aInput['value'] : '';
+            break;
+
+            default:
+                //unknown control type
+                $sInput = 'Unknown control type';
+        }
+
+        // create input label
+        $sInput .= $this->genLabel($aInput);
+
+        return $sInput;
+    }
+
+    /**
+     * Generate new Input Element id
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function getInputId(&$aInput)
+    {
+        if (isset($aInput['id']))
+            return $aInput['id'];
+
+        $sName = md5($aInput['name']);
+
+        $sID = $this->id . '_input_' . $sName;
+
+        if ( // multiple elements cause identical id's
+            (
+                (
+                    $aInput['type'] == 'checkbox' and
+                    substr($aInput['name'], -2) == '[]' // it is multiple element
+                ) or
+                $aInput['type'] == 'radio' // it is always multiple (i think so... hm)
+            ) and
+            isset($aInput['value']) // if we can make difference
+        ) {
+            $sValue = md5($aInput['value']);
+
+            // add value
+            $sID .= '_' . $sValue;
+        }
+
+        $sID = trim($sID, '_');
+
+        $aInput['id'] = $sID; // just for repeated calls
+
+        return $sID;
+    }
+
+    /**
+     * Generate standard Input Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputStandard(&$aInput)
+    {
+        // clone attributes for system use ;)
+        $aAttrs = empty($aInput['attrs']) ? array() : $aInput['attrs'];
+
+        // add default className to attributes
+        $aAttrs['type'] = $aInput['type'];
+        if ('datetime' == $aAttrs['type'])
+            $aAttrs['type'] = 'date_time';
+
+        switch($aAttrs['type']) {
+            case 'date_time':
+                $this->addCssJsUi ();
+                $this->addCssJsTimepicker ();
+                break;
+            case 'slider':
+            case 'doublerange':
+            case 'datepicker':
+                $this->addCssJsUi ();
+                break;
+            case 'text':
+                if (isset($aAttrs['class']) && false !== strpos($aAttrs['class'], 'bx-form-input-rgb'))
+                    $this->addCssJsMinicolors ();
+                break;
+        }
+
+        if ('doublerange' == $aAttrs['type'] && (!isset($aInput['value']) || !$aInput['value']) && !empty($aAttrs['min']) && !empty($aAttrs['max'])) {
+            $aInput['value'] = $aAttrs['min'] . '-' . $aAttrs['max'];
+        }
+
+        if (isset($aInput['name'])) $aAttrs['name'] = $aInput['name'];
+        if (isset($aInput['value'])) $aAttrs['value'] = $aInput['value'];
+        if (isset($aInput['db']['pass']) && ('DateUtc' == $aInput['db']['pass'] || 'DateTimeUtc' == $aInput['db']['pass'])) $aAttrs['data-utc'] = 1;
+
+        // for inputs with labels generate id
+        if (isset($aInput['label']))
+            $aAttrs['id'] = $this->getInputId($aInput);
+
+        // for checkboxes
+        if (isset($aInput['checked']) and $aInput['checked'])
+            $aAttrs['checked'] = 'checked';
+
+        $sAttrs = bx_convert_array2attrs($aAttrs, "bx-def-font-inputs bx-form-input-{$aInput['type']}");
+
+        return  "<input $sAttrs />\n";
+    }
+
+    /**
+     * Generate Switcher Input Element (based on checkbox)
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputSwitcher(&$aInput)
+    {
+        $aInput['type'] = 'checkbox';
+        $sCheckbox = $this->genInputStandard($aInput);
+        $aInput['type'] = 'switcher';
+
+        $sClass = 'off';
+        if (isset($aInput['checked']) and $aInput['checked'])
+            $sClass = 'on';
+
+        return '
+            <div class="bx-switcher-cont ' . $sClass . '">' . $sCheckbox . '
+                <div class="bx-switcher-canvas">
+                    <div class="bx-switcher-on"><i class="sys-icon check"></i></div>
+                    <div class="bx-switcher-off"><i class="sys-icon times"></i></div>
+                    <div class="bx-switcher-handler">&nbsp;</div>
+                </div>
+            </div>';
+    }
+
+    /**
+     * Generate Button Input Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputButton(&$aInput)
+    {
+        // clone attributes for system use ;)
+        $aAttrs = empty($aInput['attrs']) ? array() : $aInput['attrs'];
+
+        // add default className to attributes
+        $aAttrs['type'] = $aInput['type'];
+        if (isset($aInput['value']))
+            $aAttrs['value'] = $aInput['value'];
+
+        if (isset($aInput['name'])) $aAttrs['name'] = $aInput['name'];
+
+        $sAttrs = bx_convert_array2attrs($aAttrs, "bx-def-font-inputs bx-form-input-{$aInput['type']} bx-btn" . ('submit' == $aInput['type'] ? ' bx-btn-primary' : ''));
+
+        return  "<button $sAttrs>" . $aInput['value'] . "</button>\n";
+    }
+
+    /**
+     * Generate Textarea Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputTextarea(&$aInput)
+    {
+        // clone attributes for system use ;)
+        $aAttrs = empty($aInput['attrs']) ? array() : $aInput['attrs'];
+
+        $aAttrs['name'] = $aInput['name'];
+
+        // for inputs with labels generate id
+        if (isset($aInput['label']))
+            $aAttrs['id'] = $this->getInputId($aInput);
+
+        $sAttrs = bx_convert_array2attrs($aAttrs, "bx-def-font-inputs bx-form-input-{$aInput['type']}" . ((isset($aInput['html']) and $aInput['html'] and $this->addHtmlEditor($aInput['html'], $aInput)) ? ' bx-form-input-html' : ''));
+
+        $sValue = isset($aInput['value']) ? bx_process_output((isset($aInput['html']) && $aInput['html']) || (isset($aInput['code']) && $aInput['code']) ? $aInput['value'] : strip_tags($aInput['value'])) : '';
+
+        return "<textarea $sAttrs>$sValue</textarea>";
+    }
+
+    function isHtmlEditor($iViewMode, &$aInput)
+    {
+		return BxDolEditor::getObjectInstance(false, $this->oTemplate) !== false;
+    }
+
+    function addHtmlEditor($iViewMode, &$aInput)
+    {
+        $oEditor = BxDolEditor::getObjectInstance(false, $this->oTemplate);
+        if (!$oEditor)
+            return false;
+
+        $this->_sCodeAdd .= $oEditor->attachEditor ('#' . $this->aFormAttrs['id'] . ' [name='.$aInput['name'].']', $iViewMode, $this->_bDynamicMode);
+
+        return true;
+    }
+
+    /**
+     * Generate Select Box Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputSelectBox(&$aInput, $sInfo = '', $sError = '')
+    {
+        $aNewInput = $aInput;
+
+        $aNewInput['type'] = 'select';
+        $aNewInput['name'] .= '[]';
+
+        $sInput = $this->genInput($aNewInput);
+        return <<<BLAH
+                <div class="bx-form-input-wrapper input-wrapper-{$aInput['type']}">
+                   $sInput
+                </div>
+                $sInfo
+                $sError
+BLAH;
+
+    }
+
+    /**
+     * Generate Browse File Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputFile(&$aInput)
+    {
+    	$sOnChange = "$(this).parents('.bx-form-input-wrapper-file:first').find('.bx-fif-value').html($(this).val());";
+    	if(!empty($aInput['attrs']['onchange']))
+    		$aInput['attrs']['onchange'] = $sOnChange . $aInput['attrs']['onchange'];
+    	else
+    		$aInput['attrs']['onchange'] = $sOnChange;
+
+    	$sInput = $this->genInputStandard($aInput);
+    	return '<label class="bx-btn">' . $sInput . '<span class="bx-fif-label">' . _t('_sys_form_txt_select_file') . '</span></label><span class="bx-fif-value bx-def-margin-thd-left"></span>';
+    }
+
+    /**
+     * Generate Select Box Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputFiles(&$aInput, $sInfo = '', $sError = '')
+    {
+        $sUniqId = genRndPwd (8, false);
+        $sUploaders = '';
+        $oUploader = null;
+        foreach ($aInput['uploaders'] as $sUploaderObject) {
+            $oUploader = BxDolUploader::getObjectInstance($sUploaderObject, $aInput['storage_object'], $sUniqId, $this->oTemplate);
+            if (!$oUploader)
+                continue;
+
+            $sGhostTemplate = $this->genGhostTemplate($aInput);
+
+            $aAttrs = !empty($aInput['attrs']) ? $aInput['attrs'] : array();
+            if (empty($aInput['attrs']['disabled']))
+                $aAttrs = array_merge($aAttrs, array('onclick' => $oUploader->getNameJsInstanceUploader() . '.showUploaderForm();'));
+                
+            $aParams = array(
+                'button_title' => bx_js_string($oUploader->getUploaderButtonTitle(isset($aInput['upload_buttons_titles']) ? $aInput['upload_buttons_titles'] : false)),
+                'content_id' => isset($aInput['content_id']) ? $aInput['content_id'] : '',
+                'storage_private' => isset($aInput['storage_private']) ? $aInput['storage_private'] : '1',
+                'attrs' => bx_convert_array2attrs($aAttrs),
+                'btn_class' => !empty($aInput['attrs']['disabled']) ? 'bx-btn-disabled' : '',
+            );
+            if (isset($aInput['images_transcoder']) && $aInput['images_transcoder'])
+                $aParams['images_transcoder'] = bx_js_string($aInput['images_transcoder']);
+
+            $sUploaders .= $oUploader->getUploaderButton($sGhostTemplate, isset($aInput['multiple']) ? $aInput['multiple'] : true, $aParams, $this->_bDynamicMode);
+        }
+
+        return $this->oTemplate->parseHtmlByName('form_field_uploader.html', array(
+            'uploaders_buttons' => $sUploaders,
+            'info' => $sInfo,
+            'error' => $sError,
+            'id_container_errors' => $oUploader ? $oUploader->getIdContainerErrors() : '',
+            'id_container_result' => $oUploader ? $oUploader->getIdContainerResult() : '',
+            'uploader_instance_name' => $oUploader ? $oUploader->getNameJsInstanceUploader() : '',
+            'is_init_ghosts' => isset($aInput['init_ghosts']) && !$aInput['init_ghosts'] ? 0 : 1,
+        ));
+    }
+
+    protected function genGhostTemplate(&$aInput)
+    {
+        $sGhostTemplate = false;
+        if (isset($aInput['ghost_template']) && is_object($aInput['ghost_template'])) { // form is not submitted and ghost template is BxDolFormNested object
+
+            $oFormNested = $aInput['ghost_template'];
+            if ($oFormNested instanceof BxDolFormNested)
+                $sGhostTemplate = $oFormNested->genForm();
+
+        } elseif (isset($aInput['ghost_template']) && is_array($aInput['ghost_template']) && isset($aInput['ghost_template']['inputs'])) { // form is not submitted and ghost template is form array
+
+            $oFormNested = new BxDolFormNested($aInput['name'], $aInput['ghost_template'], $this->aParams['db']['submit_name'], $this->oTemplate);
+            $sGhostTemplate = $oFormNested->getCode();
+
+        } elseif (isset($aInput['ghost_template']) && is_array($aInput['ghost_template']) && $aInput['ghost_template']) { // form is submitted and ghost template is array of BxDolFormNested objects
+
+            $sGhostTemplate = array ();
+            foreach ($aInput['ghost_template'] as $iFileId => $oFormNested)
+                if (is_object($oFormNested) && $oFormNested instanceof BxDolFormNested)
+                    $sGhostTemplate[$iFileId] = $oFormNested->genForm();
+
+        } elseif (isset($aInput['ghost_template']) && is_string($aInput['ghost_template'])) { // ghost template is just string template, without nested form
+
+            $sGhostTemplate = $aInput['ghost_template'];
+
+        }
+
+        return $sGhostTemplate;
+    }
+
+    protected function genCustomInputUsernamesSuggestions ($aInput)
+    {
+        $this->addCssJsUi();
+
+        $sVals = '';
+        if(!empty($aInput['value']) && is_array($aInput['value'])) {
+            foreach($aInput['value'] as $sVal) {
+                if(!$sVal || !($oProfile = BxDolProfile::getInstance($sVal)))
+                    continue;
+
+               $sVals .= '<b class="val bx-def-color-bg-hl bx-def-round-corners">' . $oProfile->getUnit(0, array('template' => 'unit_wo_info')) . $oProfile->getDisplayName() . '<input type="hidden" name="' . $aInput['name'] . '[]" value="' . $sVal . '" /></b>';
+            }
+            $sVals = trim($sVals, ',');
+        }
+
+        $bDisabled = isset($aInput['attrs']['disabled']) && $aInput['attrs']['disabled'] == 'disabled';
+
+        $sId = $aInput['name'] . time() . mt_rand(0, 100);
+        $sClass = 'bx-form-input-autotoken bx-def-font-inputs bx-form-input-text';
+        if($bDisabled)
+            $sClass .= ' bx-form-input-disabled';
+        if(!empty($aInput['attrs']['class'])) {
+            $sClass .= ' ' . $aInput['attrs']['class'];
+            unset($aInput['attrs']['class']);
+        }
+
+        $aAttrs = array('value' => '', 'autocomplete' => 'off', 'autocapitalize' => 'off', 'autocorrect' => 'off');
+        if(isset($aInput['attrs']) && is_array($aInput['attrs']))
+            $aAttrs = array_merge($aAttrs, $aInput['attrs']);
+
+        return $this->oTemplate->parseHtmlByName('form_field_custom_suggestions.html', array(
+            'id' => $sId,
+            'name' => $aInput['name'],
+            'class' => $sClass,
+            'vals' => $sVals,
+            'bx_if:input' => array(
+                'condition' => !$bDisabled,
+                'content' => array(
+                    'attrs' => bx_convert_array2attrs($aAttrs),
+                )
+            ),
+            'bx_if:init' => array(
+                'condition' => !$bDisabled,
+                'content' => array(
+                    'id' => $sId,
+                    'name' => $aInput['name'],
+                    'url_get_recipients' => $aInput['ajax_get_suggestions'],
+                    'b_img' => isset($aInput['custom']['b_img']) ? (int)$aInput['custom']['b_img'] : 1,
+                    'only_once' => isset($aInput['custom']['only_once']) ? 1 : 0,
+                    'on_select' => isset($aInput['custom']['on_select']) ? $aInput['custom']['on_select']: 'null',
+                    'placeholder' => bx_html_attribute(isset($aInput['placeholder']) ? $aInput['placeholder'] : _t('_sys_form_paceholder_profiles_suggestions'), BX_ESCAPE_STR_QUOTE),
+                )
+            )
+        ));
+    }
+
+    protected function genCustomViewRowValueLabels ($aInput)
+    {        
+        if (empty($aInput['value']) || !is_array($aInput['value']) || !($oMetatags = BxDolMetatags::getObjectInstance($aInput['meta_object'])) || !$oMetatags->keywordsIsEnabled())
+            return null;
+
+        $s = ''; 
+        foreach ($aInput['value'] as $sLabel)
+            $s .= '<a href="' . $oMetatags->keywordsGetHashTagUrl($sLabel, $aInput['content_id']) . '"><b class="val bx-def-color-bg-hl bx-def-round-corners">' . trim($sLabel) . '</b></a>';
+
+        return $this->oTemplate->parseHtmlByName('form_field_labels_view.html', array(
+            'values' => $s,
+        ));
+    }
+
+    protected function genCustomInputLabels ($aInput)
+    {
+        $aValues = array();
+        $oLabel = BxDolLabel::getInstance();
+        $this->_getLabelOptions(0, $oLabel, $aValues);
+
+        $aInput['type'] = 'select_multiple';
+        $aInput['values'] = $aValues;
+
+        return $this->genInputSelectMultiple($aInput);
+    }
+
+    protected function _getLabelOptions($iParent, &$oLabel, &$aValues)
+    {
+        $aLabels = $oLabel->getLabels(array('type' => 'parent', 'parent' => $iParent));
+        foreach($aLabels as $aLabel) {
+            $aValues[] = array('key' => $aLabel['value'], 'value' => str_repeat('&nbsp;&nbsp;&nbsp;', (int)$aLabel['level']) . ' ' . $aLabel['value']);
+
+            $this->_getLabelOptions($aLabel['id'], $oLabel, $aValues);
+        }
+    }
+
+    /*
+    protected function genCustomInputLabels ($aInput)
+    {
+        // generate currently selected labels
+        $sValue = '';
+        if (!empty($aInput['value']) && is_array($aInput['value'])) {
+            sort($aInput['value'], SORT_LOCALE_STRING);
+            foreach ($aInput['value'] as $sVal) {
+               $sValue .= '<b class="val bx-def-color-bg-hl bx-def-round-corners">' . trim($sVal) . '<input type="hidden" name="' . $aInput['name'] . '[]" value="' . trim($sVal) . '" /></b>';
+            }
+            $sValue = trim($sValue, ',');
+        }
+
+        // generate "following" labels
+        $aFollowingLabels = bx_srv('bx_channels', 'get_following_channels_names', array(bx_get_logged_profile_id()));
+        if (!is_array($aFollowingLabels))
+            $aFollowingLabels = array();
+        $aFollowingLabels = array_intersect($aFollowingLabels, $aInput['values']);
+        $aFollowingLabels = array_diff($aFollowingLabels, !empty($aInput['value']) && is_array($aInput['value']) ? $aInput['value'] : array());
+        $aValues = array_diff($aInput['values'], 
+            !empty($aInput['value']) && is_array($aInput['value']) ? $aInput['value'] : array(), 
+            !empty($aFollowingLabels) && is_array($aFollowingLabels) ? $aFollowingLabels : array());
+
+        $sValues = '';
+        if (!empty($aFollowingLabels)) {
+            sort($aFollowingLabels, SORT_LOCALE_STRING);
+            foreach ($aFollowingLabels as $sVal) {
+               $sValues .= '<b class="val bx-def-color-bg-hl bx-def-round-corners">' . trim($sVal) . '</b>';
+            }
+            if (!empty($aValues))
+                $sValues .= '<a href="#" class="bx-form-input-autotoken-labels-show-more">' . _t('_sys_show_more') . '</a><i class="bx-form-input-autotoken-labels-more">';
+        }
+
+        // generate all possible labels 
+        if (!empty($aValues)) {
+            sort($aValues, SORT_LOCALE_STRING);
+            foreach ($aValues as $sVal) {
+               $sValues .= '<b class="val bx-def-color-bg-hl bx-def-round-corners">' . trim($sVal) . '</b>';
+            }
+            $sValues = trim($sValues, ',');
+        }
+
+        if (!empty($aFollowingLabels) && !empty($aValues))
+            $sValues .= '</i>';
+
+        return $this->oTemplate->parseHtmlByName('form_field_labels.html', array(
+            'id' => $aInput['name'] . time() . mt_rand(0, 100),
+            'name' => $aInput['name'],
+            'value' => $sValue,
+            'values' => $sValues,
+        ));
+    }
+     */
+    
+    /**
+     * Generate Select Element	
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputSelect(&$aInput)
+    {
+        $sCurValue = isset($aInput['value']) ? $aInput['value'] : '';
+        return $this->_genInputSelect($aInput, false, $sCurValue, '_isSelected');
+    }
+
+    /**
+     * Generate Multiple Select Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputSelectMultiple(&$aInput)
+    {
+        $aCurValues = array();
+        if (isset($aInput['value']) && $aInput['value'])
+            $aCurValues = is_array($aInput['value']) ? $aInput['value'] : array();
+        return $this->_genInputSelect($aInput, true, $aCurValues, '_isSelectedMultiple');
+    }
+
+    /**
+     * Generate Checkbox Set Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputCheckboxSet(&$aInput)
+    {
+        $aCurValues = array();
+        if (isset($aInput['value']) && $aInput['value'])
+            $aCurValues = is_array($aInput['value']) ? $aInput['value'] : array();
+        return $this->_genInputsSet($aInput, 'checkbox', $aCurValues, '_isSelectedMultiple', '[]');
+    }
+    /**
+     * Generate Radiobuttons Set Element
+     *
+     * @param  array  $aInput
+     * @return string
+     */
+    function genInputRadioSet(&$aInput)
+    {
+        $sCurValue = isset($aInput['value']) ? $aInput['value'] : '';
+        return $this->_genInputsSet($aInput, 'radio', $sCurValue, '_isSelected');
+    }
+
+    function _isSelected ($sValue, $sCurValue)
+    {
+        return ((string)$sValue === (string)$sCurValue);
+    }
+
+    function _isSelectedMultiple ($sValue, $aCurValues)
+    {
+        return in_array($sValue, $aCurValues);
+    }
+
+    function _genInputSelect(&$aInput, $isMultiple, $mixedCurrentVal, $sIsSelectedFunc)
+    {
+        $aAttrs = empty($aInput['attrs']) ? array() : $aInput['attrs'];
+
+        $aAttrs['name'] = $aInput['name'];
+        if ($isMultiple) {
+            $aAttrs['name'] .= '[]';
+            $aAttrs['multiple'] = 'multiple';
+        }
+
+        // for inputs with labels generate id
+        if (isset($aInput['label']))
+            $aAttrs['id'] = $this->getInputId($aInput);
+
+        $sAttrs = bx_convert_array2attrs($aAttrs, "bx-def-font-inputs bx-form-input-{$aInput['type']}");
+
+        // generate options
+        $sOptions = '';
+        if (isset($aInput['values']) and is_array($aInput['values'])) {
+            foreach ($aInput['values'] as $sValue => $sTitle) {
+                $sAttrsOpt = "";
+                if(is_array($sTitle)) {
+                    if(isset($sTitle['type'])) {
+                        switch($sTitle['type']) {
+                            case 'group_header':
+                                $sTitle = bx_process_output($sTitle['value']);
+                                $sOptions .= <<<BLAH
+                                       <optgroup label="$sTitle">
+BLAH;
+                                break;
+                            case 'group_end':
+                                $sOptions .= <<<BLAH
+                                       </optgroup>
+BLAH;
+                                break;
+                        }
+                        continue;
+                    }
+
+                    $aAttrsOpt = array();
+                    if(!empty($sTitle['attrs']) && is_array($sTitle['attrs']))
+                        $aAttrsOpt = $sTitle['attrs'];
+
+                    if(isset($sTitle['class']))
+                        $aAttrsOpt['class'] = !empty($aAttrsOpt['class']) ? $aAttrsOpt['class'] . ' ' . $sTitle['class'] : $sTitle['class'];
+
+                    if(isset($sTitle['style']))
+                        $aAttrsOpt['style'] = !empty($aAttrsOpt['style']) ? $aAttrsOpt['style'] . ' ' . $sTitle['style'] : $sTitle['style'];
+
+                    $sAttrsOpt = !empty($aAttrsOpt) ? bx_convert_array2attrs($aAttrsOpt) : '';
+
+                    $sValue = $sTitle['key'];
+                    $sTitle = $sTitle['value'];
+                }
+                $sValueC = bx_html_attribute($sValue);
+                $sTitleC = bx_process_output($sTitle);
+
+                $sSelected = $this->$sIsSelectedFunc($sValue, $mixedCurrentVal) ? 'selected="selected"' : '';
+
+                $sOptions .= <<<BLAH
+                   <option value="$sValueC" $sAttrsOpt $sSelected>$sTitleC</option>
+BLAH;
+
+            }
+        }
+
+        // generate element
+        $sCode = <<<BLAH
+            <select $sAttrs>
+                $sOptions
+            </select>
+BLAH;
+
+        if(!empty($aInput['content']))
+            $sCode .= $aInput['content'];
+
+        return $sCode;
+    }
+
+    function _genInputsSet(&$aInput, $sType, $mixedCurrentVal, $sIsCheckedFunc, $sNameAppend = '')
+    {
+        $aAttrs = empty($aInput['attrs']) || 'radio_set' == $aInput['type'] || 'checkbox_set' == $aInput['type'] ? array() : $aInput['attrs'];
+
+        $aAttrs['name']  = $aInput['name'];
+
+        // for inputs with labels generate id
+        if (isset($aInput['label']))
+            $aAttrs['id'] = $this->getInputId($aInput);
+
+        $sAttrs = bx_convert_array2attrs($aAttrs, "bx-form-input-{$aInput['type']}");
+
+        // generate options
+        $sDivider = isset($aInput['dv']) ? $aInput['dv'] : $this->_sDivider;
+
+        $sOptions = '';
+
+        if (isset($aInput['values']) && is_array($aInput['values'])) {
+            if (count($aInput['values']) > 3 && $sDivider == $this->_sDivider)
+                $sDivider = $this->_sDividerAlt;
+            
+            // generate complex input using simple standard inputs
+            foreach ($aInput['values'] as $sValue => $sLabel) {
+                if(is_array($sLabel)) {
+                    $sValue = $sLabel['key'];
+                    $sLabel = $sLabel['value'];
+                }
+
+                // create new simple input
+                $aNewInput = array(
+                    'type'    => $sType,
+                    'name'    => $aInput['name'] . $sNameAppend,
+                    'value'   => $sValue,
+                    'checked' => $this->$sIsCheckedFunc($sValue, $mixedCurrentVal),
+                    'label'   => $sLabel,
+                    'attrs'   => !empty($aInput['attrs']) && ('radio_set' == $aInput['type'] || 'checkbox_set' == $aInput['type']) ? $aInput['attrs'] : false,
+                );
+
+                $sNewInput  = $this->genInput($aNewInput);
+
+                // attach new input to complex
+                $sOptions .= ($sNewInput . $sDivider);
+            }
+        }
+
+        // generate element
+        $sCode = <<<BLAH
+            <div $sAttrs>
+                $sOptions
+            </div>
+BLAH;
+
+        return $sCode;
+    }
+
+    function genInputLocation(&$aInput)
+    {
+        $isManualInput = (int)(isset($aInput['manual_input']) && $aInput['manual_input']);
+        $sIdStatus = $this->getInputId($aInput) . '_status';
+        $sIdInput = $this->getInputId($aInput) . '_location';
+        $sProto = (0 === strncmp('https', BX_DOL_URL_ROOT, 5)) ? 'https' : 'http';
+
+        $aVars = array (
+            'key' => trim(getParam('sys_maps_api_key')),
+            'lang' => bx_lang_name(),
+            'name' => $aInput['name'],
+            'id_status' => $sIdStatus,
+            'id_input' => $sIdInput,
+            'manual_input' => $isManualInput,            
+            'bx_if:manual_input' => array(
+                'condition' => $isManualInput,
+                'content' => array(),
+            ),
+            'bx_if:auto_input' => array(
+                'condition' => !$isManualInput,
+                'content' => array(
+                    'id_status' => $sIdStatus,
+                    'location_string' => _t('_sys_location_undefined'),
+                ),
+            ),            
+        );
+        $aLocationIndexes = array ('lat', 'lng', 'country', 'state', 'city', 'zip', 'street', 'street_number');
+        foreach ($aLocationIndexes as $sKey)
+            $aVars[$sKey] = $this->getLocationVal($aInput, $sKey);
+        if ($aVars['country']) {
+            $aCountries = BxDolFormQuery::getDataItems('Country');
+            $s = ($aVars['street_number'] ? $aVars['street_number'] . ', ' : '') . ($aVars['street'] ? $aVars['street'] . ', ' : '') . ($aVars['city'] ? $aVars['city'] . ', ' : '') . ($aVars['state'] ? $aVars['state'] . ', ' : '') . $aCountries[$aVars['country']];
+            $aVars['bx_if:auto_input']['content']['location_string'] = $s;
+        }
+
+        if ($isManualInput) {
+            $aInput['type'] = 'text';
+            $aInput['attrs']['id'] = $sIdInput;
+            $aVars['input'] = $this->genInputStandard($aInput);
+        } 
+        else {
+            if ($this->getLocationVal($aInput, 'lat') && $this->getLocationVal($aInput, 'lng'))
+                $aInput['checked'] = true;
+            else
+                $aInput['checked'] = $this->getCleanValue($aInput['name'] . '_lat') && $this->getCleanValue($aInput['name'] . '_lng') ? 1 : 0;
+            $aVars['input'] = $this->genInputSwitcher($aInput);
+        }
+
+        return $this->oTemplate->parseHtmlByName('form_field_location.html', $aVars);
+    }
+
+    public function setLocationVal ($aInput, $sIndex, $sVal)
+    {
+        $s = $aInput['name'] . '_' . $sIndex;
+        $this->_aSpecificValues[$s] = $sVal;
+    }
+
+    protected function getLocationVal ($aInput, $sIndex) 
+    {
+        $s = $aInput['name'] . '_' . $sIndex;
+        if (isset($this->_aSpecificValues[$s]))
+            return $this->_aSpecificValues[$s];
+        return $this->getCleanValue($s);
+    }
+    
+    function genInputCaptcha(&$aInput)
+    {
+        $aAttrs = empty($aInput['attrs']) ? array() : $aInput['attrs'];
+
+        // for inputs with labels generate id
+        if (isset($aInput['label']))
+            $aAttrs['id'] = $this->getInputId($aInput);
+
+        $sAttrs = bx_convert_array2attrs($aAttrs, "bx-form-input-{$aInput['type']}");
+
+        $oCaptcha = BxDolCaptcha::getObjectInstance();
+
+        return "<div $sAttrs>" . ($oCaptcha ? $oCaptcha->display($this->_bDynamicMode) : _t('_sys_txt_captcha_not_available')) . "</div>";
+    }
+
+    /**
+     * Generate Label Element
+     *
+     * @param  string $sLabel   Text of the Label
+     * @param  string $sInputID Dependant Input Element ID
+     * @return string HTML code
+     */
+    function genLabel(&$aInput)
+    {
+        if (!isset($aInput['label']) or empty($aInput['label']))
+            return '';
+
+        $sLabel   = $aInput['label'];
+        $sInputID = $this->getInputId($aInput);
+
+        $sRet = '<label for="' . $sInputID . '">' . bx_process_output($sLabel) . '</label>';
+
+        return $sRet;
+    }
+
+    function genInfoIcon($sInfo)
+    {
+        return '<div class="bx-form-info bx-def-font-grayed bx-def-font-small">' . bx_process_output($sInfo, BX_DATA_HTML) . '</div>';
+    }
+
+    function genErrorIcon( $sError = '' )
+    {
+        if ($this->bEnableErrorIcon) {
+            $sStyle = '';
+            if (!$sError)
+                $sStyle = 'style="display:none;"';
+            return '<div class="bx-form-warn" ' . $sStyle . '>' . $sError . '</div>';
+        }
+    }
+
+    function getOpenSection($aAttrs = array(), $sLegend = '')
+    {
+        if (!$this->_isSectionOpened) {
+
+            if (!$aAttrs || !is_array($aAttrs))
+                $aAttrs = array();
+
+            if ($sLegend)
+                $sClassesAdd = "bx-form-section-header";
+            else
+                $sClassesAdd = "bx-form-section-divider";
+
+            $sAttrs = bx_convert_array2attrs($aAttrs, "bx-form-section bx-def-padding-sec-top bx-def-border-top " . $sClassesAdd);
+
+            $this->_isSectionOpened = true;
+
+            return "<!-- form header content begins -->\n <div class=\"bx-form-section-wrapper bx-def-margin-top\"> <div $sAttrs> $sLegend <div class=\"bx-form-section-content bx-def-padding-top bx-def-padding-bottom" . ($sLegend ? ' bx-def-padding-left bx-def-padding-right' : '') . "\">\n";
+
+        } else {
+
+            return '';
+        }
+    }
+
+    function getCloseSection()
+    {
+        if ($this->_isSectionOpened) {
+
+            $this->_isSectionOpened = false;
+            return "</div> </div> </div> \n<!-- form header content ends -->\n";
+
+        } else {
+
+            return '';
+        }
+    }
+
+    function getOpenSectionViewMode($aAttrs = array(), $sLegend = '')
+    {
+        if (!$this->_isSectionOpened) {
+
+            if (!$aAttrs || !is_array($aAttrs))
+                $aAttrs = array();
+
+            if ($sLegend)
+                $sClassesAdd = "bx-form-section-header";
+            else
+                $sClassesAdd = "bx-form-section-divider";
+
+            $sAttrs = bx_convert_array2attrs($aAttrs, "bx-form-section bx-form-view-section bx-def-padding-sec-top bx-def-border-top " . $sClassesAdd);
+
+            $this->_isSectionOpened = true;
+
+            return "<!-- form header content begins -->\n <div class=\"bx-form-section-wrapper bx-def-margin-top\"> <div $sAttrs> $sLegend <div class=\"bx-form-section-content bx-def-padding-top\">\n";
+
+        } else {
+
+            return '';
+        }
+    }
+
+    function getCloseSectionViewMode()
+    {
+        if ($this->_isSectionOpened) {
+
+            $this->_isSectionOpened = false;
+            return "</div> </div> </div> \n<!-- form header content ends -->\n";
+
+        } else {
+
+            return '';
+        }
+    }
+
+    static function getJsUiLangs ()
+    {
+        return array ('af' => 1, 'ar-DZ' => 1, 'ar' => 1, 'az' => 1, 'be' => 1, 'bg' => 1, 'bs' => 1, 'ca' => 1, 'cs' => 1, 'cy-GB' => 1, 'da' => 1, 'de' => 1, 'el' => 1, 'en-AU' => 1, 'en-GB' => 1, 'en-NZ' => 1, 'en' => 1, 'eo' => 1, 'es' => 1, 'et' => 1, 'eu' => 1, 'fa' => 1, 'fi' => 1, 'fo' => 1, 'fr-CA' => 1, 'fr-CH' => 1, 'fr' => 1, 'gl' => 1, 'he' => 1, 'hi' => 1, 'hr' => 1, 'hu' => 1, 'hy' => 1, 'id' => 1, 'is' => 1, 'it' => 1, 'ja' => 1, 'ka' => 1, 'kk' => 1, 'km' => 1, 'ko' => 1, 'ky' => 1, 'lb' => 1, 'lt' => 1, 'lv' => 1, 'mk' => 1, 'ml' => 1, 'ms' => 1, 'nb' => 1, 'nl-BE' => 1, 'nl' => 1, 'nn' => 1, 'no' => 1, 'pl' => 1, 'pt-BR' => 1, 'pt' => 1, 'rm' => 1, 'ro' => 1, 'ru' => 1, 'sk' => 1, 'sl' => 1, 'sq' => 1, 'sr-SR' => 1, 'sr' => 1, 'sv' => 1, 'ta' => 1, 'th' => 1, 'tj' => 1, 'tr' => 1, 'uk' => 1, 'vi' => 1, 'zh-CN' => 1, 'zh-HK' => 1, 'zh-TW' => 1);
+    }
+    
+    function addCssJsUi ()
+    {
+        if (self::$_isCssJsUiAdded)
+            return;
+
+        $aUiLangs = $this->getJsUiLangs ();
+
+        $sUiLang = BxDolLanguages::getInstance()->detectLanguageFromArray ($aUiLangs);
+
+        $this->_addJs(array(
+            'jquery-ui/jquery-ui.custom.min.js',
+            'jquery-ui/i18n/jquery.ui.datepicker-' . $sUiLang . '.min.js',
+        ), "'undefined' === typeof($.datepicker)");
+        
+        $this->_addCss('jquery-ui/jquery-ui.css');
+
+        self::$_isCssJsUiAdded = true;
+    }
+
+    function addCssJsTimepicker ()
+    {
+        if (self::$_isCssJsTimepickerAdded)
+            return; 
+
+        $aCalendarLangs = array ('af' => 1, 'am' => 1, 'bg' => 1, 'ca' => 1, 'cs' => 1, 'da' => 1, 'de' => 1, 'el' => 1, 'es' => 1, 'et' => 1, 'eu' => 1, 'fa' => 1, 'fi' => 1, 'fr' => 1, 'gl' => 1, 'he' => 1, 'hr' => 1, 'hu' => 1, 'id' => 1, 'it' => 1, 'ja' => 1, 'ko' => 1, 'lt' => 1, 'lv' => 1, 'mk' => 1, 'nl' => 1, 'no' => 1, 'pl' => 1, 'pt-BR' => 1, 'pt' => 1, 'ro' => 1, 'ru' => 1, 'sk' => 1, 'sl' => 1, 'sr-RS' => 1, 'sr-YU' => 1, 'sv' => 1, 'th' => 1, 'tr' => 1, 'uk' => 1, 'vi' => 1, 'zh-CN' => 1, 'zh-TW' => 1);
+
+        $sCalendarLang = BxDolLanguages::getInstance()->detectLanguageFromArray ($aCalendarLangs);            
+        $this->_addCss('timepicker-addon/jquery-ui-timepicker-addon.css');
+        
+        $this->_addJs(array(
+            'timepicker-addon/jquery-ui-timepicker-addon.min.js',
+            'timepicker-addon/jquery-ui-sliderAccess.js',
+            'timepicker-addon/i18n/jquery-ui-timepicker-' . $sCalendarLang . '.js',
+            'jquery.ui.touch-punch.min.js',
+        ), "'undefined' === typeof($.fn.datetimepicker)");
+
+        self::$_isCssJsTimepickerAdded = true;
+    }
+
+    function addCssJsMinicolors ()
+    {
+        if (self::$_isCssJsMinicolorsAdded)
+            return;       
+
+        $this->_addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'jquery-minicolors/|jquery.minicolors.css');
+        $this->_addJs('jquery-minicolors/jquery.minicolors.min.js', "'undefined' === typeof($.minicolors)");
+
+        self::$_isCssJsMinicolorsAdded = true;
+    }
+
+    function addCssJsViewMode ()
+    {
+        if (self::$_isCssJsAddedViewMode)
+            return;
+
+        $this->_addCss('forms.css');
+
+        self::$_isCssJsAddedViewMode = true;
+    }
+
+    function addCssJsCore ()
+    {
+        if (self::$_isCssJsAdded)
+            return;
+
+        $this->_addCss('forms.css');
+        $this->_addJs('jquery.webForms.js', "'undefined' === typeof($.fn.addWebForms)");
+
+        self::$_isCssJsAdded = true;
+    }
+
+    function addCssJs ()
+    {
+        if (isset($this->aParams['view_mode']) && $this->aParams['view_mode']) {
+
+            $this->addCssJsViewMode ();
+
+        } else { 
+
+            $this->addCssJsCore();            
+        }
+    }
+
+    protected function _processCssJs()
+    {
+        $sRet = '';
+        if ($this->_bDynamicMode) {
+            $sRet .= $this->oTemplate->addCss($this->_aCss, true);
+
+
+            $sRet .= "\n<script>\n";
+            $sRet .= "\nglJsLoadOnaddWebForms = [];\n";
+            foreach ($this->_aJs as $sCondition => $aJs) {
+                $sJs = $this->oTemplate->addJs($aJs, true);
+                if (!$sJs)
+                    continue;
+                
+                $sRet .= "if ($sCondition)\n";
+                if (preg_match_all("/src=\"(.*?)\"/", $sJs, $aMatches))
+                    $sRet .= "\tglJsLoadOnaddWebForms = glJsLoadOnaddWebForms.concat(" . json_encode($aMatches[1]) . ");\n";
+            }
+            $sRet .= "\n</script>\n";
+        }
+        else {
+            $this->oTemplate->addCss($this->_aCss);
+            
+            $aJs = array();
+            foreach ($this->_aJs as $sCondition => $a)
+                $aJs = array_merge($aJs, $a);
+            $this->oTemplate->addJs($aJs);
+        }
+        return $sRet;
+    }
+
+    protected function _addJs($mixed, $sJsCondition)
+    {
+        if (!is_array($mixed))
+            $mixed = array($mixed);
+        $this->_aJs[$sJsCondition] = $mixed;
+    }
+
+    protected function _addCss($mixed)
+    {
+        if (!is_array($mixed))
+            $mixed = array($mixed);
+        foreach ($mixed as $s)
+            $this->_aCss[$s] = $s;
+    }
+}
+
+/** @} */

+ 216 - 0
upgrade/files/10.0.0-10.1.0.B1/files/template/scripts/BxBaseServiceLogin.php

@@ -0,0 +1,216 @@
+<?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
+ * @{
+ */
+
+/**
+ * System service for login form functionality.
+ */
+class BxBaseServiceLogin extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceTest ($n = 1)
+    {
+        return $n*2;
+    }
+
+    public function serviceMemberAuthCode($aAuthTypes = array())
+    {
+        if(empty($aAuthTypes) || !is_array($aAuthTypes))
+            $aAuthTypes = BxDolDb::getInstance()->fromCache('sys_objects_auths', 'getAll', 'SELECT * FROM `sys_objects_auths`');
+
+        $bCompact = getParam('site_login_social_compact') == 'on';
+
+        $aTmplButtons = array();
+        foreach($aAuthTypes as $iKey => $aItems) {
+            $sTitle = _t($aItems['Title']);
+
+            $aTmplButtons[] = array( 
+            	'class' => ($bCompact ? 'sys-auth-compact bx-def-margin-sec-left-auto ' : '') . $aItems['Name'],
+                'href' => !empty($aItems['Link']) ? BX_DOL_URL_ROOT . $aItems['Link'] : 'javascript:void(0)',
+                'title_alt' => bx_html_attribute($sTitle),
+                'bx_if:show_onclick' => array(
+                    'condition' => !empty($aItems['OnClick']),
+                    'content' => array(
+                        'onclick' => 'javascript:' . $aItems['OnClick']
+                    )
+                ),
+                'bx_if:show_icon' => array(
+                    'condition' => !empty($aItems['Icon']),
+                    'content' => array(
+                        'icon' => $aItems['Icon']
+                    )
+                ),
+                'bx_if:show_title' => array(
+                    'condition' => !$bCompact || empty($aItems['Icon']),
+                    'content' => array(
+                		'title' => $sTitle
+                    )
+                )
+            );
+            if ($aItems['Style'] != ""){
+                $aStyles = unserialize($aItems['Style']);
+                foreach($aStyles as $sKey => $aValues) {
+                    BxDolTemplate::getInstance()->addCssStyle('.'. $aItems['Name'] .' ' . $sKey, $aValues);
+                }
+            }
+        }
+
+        if(empty($aTmplButtons) || !is_array($aTmplButtons))
+            return '';
+
+        $oTemplate = BxDolTemplate::getInstance();
+        $oTemplate->addCss(array('auth.css'));
+        return $oTemplate->parseHtmlByName('auth.html', array(
+            'bx_repeat:buttons' => $aTmplButtons,
+            'class_container' => ($bCompact ? 'sys-auth-compact-container' : '')
+        ));
+    }
+
+    public function serviceLoginFormOnly ($sParams = '', $sForceRelocate = '')
+    {
+    	if(strpos($sParams, 'no_join_text') === false)
+    		$sParams = ($sParams != '' ? ' ' : '') . 'no_join_text';
+
+    	return $this->serviceLoginForm($sParams, $sForceRelocate);
+    }
+
+    public function serviceLoginForm ($sParams = '', $sForceRelocate = '')
+    {
+        if (isLogged() && 'login' == bx_get('i')) {
+            header('Location: ' . BX_DOL_URL_ROOT);
+            exit;
+        }
+
+        $oForm = BxDolForm::getObjectInstance('sys_login', 'sys_login');
+
+        $sCustomHtmlBefore = '';
+        $sCustomHtmlAfter = '';
+        bx_alert('profile', 'show_login_form', 0, 0, array('oForm' => &$oForm, 'sParams' => &$sParams, 'sCustomHtmlBefore' => &$sCustomHtmlBefore, 'sCustomHtmlAfter' => &$sCustomHtmlAfter, 'aAuthTypes' => &$aAuthTypes));
+
+        if (isset($oForm->aInputs['relocate'])) {
+            if ($sForceRelocate && 0 === mb_stripos($sForceRelocate, BX_DOL_URL_ROOT))
+                $oForm->aInputs['relocate']['value'] = $sForceRelocate;
+            elseif ('homepage' == $sForceRelocate)
+                $oForm->aInputs['relocate']['value'] = BX_DOL_URL_ROOT;
+        }
+        
+        $sFormCode = $oForm->getCode();
+
+        BxDolTemplate::getInstance()->addJs(array('jquery.form.min.js'));
+        
+        $sJoinText = '';
+        if (strpos($sParams, 'no_join_text') === false)
+            $sJoinText = '<hr class="bx-def-hr bx-def-margin-sec-topbottom" /><div>' . _t('_sys_txt_login_description', BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=create-account')) . '</div>';
+
+        $sAuth = '';
+        if (strpos($sParams, 'no_auth_buttons') === false)
+            $sAuth = $this->serviceMemberAuthCode();
+
+        $sAjaxForm = '';
+        if (strpos($sParams, 'ajax_form') !== false)
+            $sAjaxForm = "<script>
+                (bx_login_init_ajax_form = function () {
+                    $('#sys-form-login').ajaxForm({
+                        dataType: 'json',
+                        success: function (oData) {
+                            if ('undefined' !== typeof(oData['res']) && 'OK' == oData['res']) {
+                                location.reload();
+                            }
+                            else if ('undefined' !== typeof(oData['form'])) {
+                                $('#sys-form-login').replaceWith(oData['form']);
+                                bx_login_init_ajax_form();
+                            }
+                        }
+                    });
+                })();
+            </script>";
+        
+        return $sCustomHtmlBefore . $sAuth . $sFormCode . $sCustomHtmlAfter . $sJoinText . $sAjaxForm;
+
+    }
+    
+    public function serviceLoginFormStep2 ()
+    {
+        if(isLogged()){
+            header('Location: ' . BX_DOL_URL_ROOT);
+            exit;
+        }
+
+        $oSession = BxDolSession::getInstance();
+        $iAccountId = $oSession->getValue(BX_ACCOUNT_SESSION_KEY_FOR_2FA_LOGIN_ACCOUNT_ID);
+        if ($iAccountId == '')
+            return false;
+
+        $oForm = BxDolForm::getObjectInstance('sys_login', 'sys_login_step2');
+        $oForm->aFormAttrs['action'] = '';
+        
+        $oAccount = BxDolAccount::getInstance($iAccountId);
+        $a = $oAccount->getInfo($iAccountId);
+        $sPhoneNumber = trim($a['phone']);
+            
+        $oForm->initChecker(array('phone' => $sPhoneNumber));
+        if ($oForm->isSubmittedAndValid()) {
+            $sNewPhoneNumber = trim($oForm->getCleanValue('phone'));
+            if ($sPhoneNumber != $sNewPhoneNumber)
+                $oAccount->updatePhone($sNewPhoneNumber);
+            $sActivationCode = rand(1000, 9999);
+            $sActivationText =_t('_sys_txt_login_2fa_sms_text', $sActivationCode);
+            $ret = null;
+            bx_alert('account', 'before_2fa_send_sms', $oAccount->id(), false, array('phone_number' => $sPhoneNumber, 'sms_text' => $sActivationText, 'override_result' => &$ret));
+            if ($ret === null) 
+            {
+                $oTwilio = BxDolTwilio::getInstance();
+                if(!$oTwilio->sendSms($sPhoneNumber,  $sActivationText)){
+                    return MsgBox(_t('_sys_txt_login_2fa_sms_error_occured'));
+                }
+            }
+            $oSession = BxDolSession::getInstance();
+            $oSession->setValue(BX_ACCOUNT_SESSION_KEY_FOR_PHONE_ACTIVATEION_CODE, $sActivationCode);
+            header('Location: ' . BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=login-step3'));
+        }
+        return '<div class="bx-def-padding-sec-bottom">' . _t("_sys_txt_login_2fa_description") . '</div>' .$oForm->getCode();
+    }
+    
+    public function serviceLoginFormStep3 ()
+    {
+        if(isLogged()){
+            header('Location: ' . BX_DOL_URL_ROOT);
+            exit;
+        }
+        
+        $oForm = BxDolForm::getObjectInstance('sys_login', 'sys_login_step3');
+        $oForm->aFormAttrs['action'] = '';
+        $oForm->initChecker();
+        
+        if ($oForm->isSubmittedAndValid()) {
+            $oSession = BxDolSession::getInstance();
+            $oSession->unsetValue(BX_ACCOUNT_SESSION_KEY_FOR_PHONE_ACTIVATEION_CODE);
+            $iAccountId = $oSession->getValue(BX_ACCOUNT_SESSION_KEY_FOR_2FA_LOGIN_ACCOUNT_ID);
+            $oAccount = BxDolAccount::getInstance($iAccountId);
+            $aAccount = bx_login($oAccount->id(), $oSession->getValue(BX_ACCOUNT_SESSION_KEY_FOR_2FA_LOGIN_IS_REMEMBER));
+           
+            $sUrlRelocate = $oForm->getCleanValue('relocate');
+            if (!$sUrlRelocate || 0 !== strncmp($sUrlRelocate, BX_DOL_URL_ROOT, strlen(BX_DOL_URL_ROOT)))
+                $sUrlRelocate = BX_DOL_ROLE_ADMIN == $oForm->getRole() ? BX_DOL_URL_STUDIO . 'launcher.php' : BX_DOL_URL_ROOT . 'member.php';
+            
+            BxDolTemplate::getInstance()->setPageNameIndex (BX_PAGE_TRANSITION);
+            BxDolTemplate::getInstance()->setPageHeader (_t('_Please Wait'));
+            BxDolTemplate::getInstance()->setPageContent ('page_main_code', MsgBox(_t('_Please Wait')));
+            BxDolTemplate::getInstance()->setPageContent ('url_relocate', bx_html_attribute($sUrlRelocate, BX_ESCAPE_STR_QUOTE));
+            
+            header('Location: ' . $sUrlRelocate);
+        } 
+        return $oForm->getCode();
+    }
+}
+
+/** @} */

+ 18 - 0
upgrade/files/10.0.0-10.1.0.B1/script.php

@@ -0,0 +1,18 @@
+<?php
+
+    $oDb = $this->oDb;
+
+    $aCmtsIds = $oDb->getAll("SELECT `id`, `system_id` FROM `sys_cmts_ids` WHERE 1");
+    $aCmtsSystems = $oDb->getColumn("SELECT `ID` FROM `sys_objects_cmts` WHERE 1");
+    foreach($aCmtsIds as $aCmtsId) {
+        if(in_array($aCmtsId['system_id'], $aCmtsSystems))
+            continue;
+
+        $oDb->query("DELETE FROM `sys_cmts_ids` WHERE `id`=:id", array(
+            'id' => $aCmtsId['id']
+        ));
+    }
+
+    $oDb->getAll("OPTIMIZE TABLE `sys_cmts_ids`");
+
+    return true;

+ 14 - 0
upgrade/files/10.0.0-10.1.0.B1/sql.sql

@@ -0,0 +1,14 @@
+
+UPDATE `sys_menu_templates` SET `visible` = 0 WHERE `template` = 'menu_interactive_vertical.html';
+UPDATE `sys_menu_templates` SET `visible` = 0 WHERE `template` = 'menu_interactive.html';
+
+DELETE FROM `sys_menu_items` WHERE `set_name` = 'sys_cmts_item_actions' AND `name` = 'item-quote';
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `active`, `copyable`, `editable`, `order`, `visibility_custom`) VALUES 
+('sys_cmts_item_actions', 'system', 'item-quote', '_sys_menu_item_title_system_cmts_item_quote', '_sys_menu_item_title_cmts_item_quote', 'javascript:void(0)', 'javascript:{quote_onclick}', '_self', 'quote-right', '', '', 0, 2147483647, 1, 0, 1, 4, '');
+
+UPDATE `sys_menu_items` SET `order` = 5 WHERE `set_name` = 'sys_cmts_item_actions' AND `name` = 'item-more' AND `order` = 4;
+
+-- Last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '10.1.0-B1' WHERE (`version` = '10.0.0') AND `name` = 'system';
+