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

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

AntonLV 7 лет назад
Родитель
Сommit
1c115d4c98
82 измененных файлов с 23162 добавлено и 3 удалено
  1. 1 1
      inc/version.inc.php
  2. 1 1
      install/index.php
  3. 1 1
      modules/base/profile/classes/BxBaseModProfileDb.php
  4. 9 0
      upgrade/files/10.0.0.B1-10.0.0.B2/check.php
  5. 90 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolGridQuery.php
  6. 121 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolMenuQuery.php
  7. 154 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolMetatagsQuery.php
  8. 212 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolModuleQuery.php
  9. 229 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolObjectQuery.php
  10. 277 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolRelation.php
  11. 128 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolSearchExtended.php
  12. 148 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolSearchExtendedQuery.php
  13. 2840 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolTemplate.php
  14. 78 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolViewQuery.php
  15. 189 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolVoteReactions.php
  16. 1741 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/utils.inc.php
  17. 12 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/version.inc.php
  18. 86 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/files/classes/BxBaseModFilesFormsEntryHelper.php
  19. 258 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralDb.php
  20. 578 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php
  21. 1763 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralModule.php
  22. 126 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralPageEntry.php
  23. 250 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/template/css/main.css
  24. 726 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/groups/classes/BxBaseModGroupsModule.php
  25. 86 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/groups/classes/BxBaseModGroupsSearchResult.php
  26. 190 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/notifications/classes/BxBaseModNotificationsConfig.php
  27. 104 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/classes/BxBaseModProfileDb.php
  28. 1553 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/classes/BxBaseModProfileModule.php
  29. 489 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/template/css/main.css
  30. 6 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/template/unit_meta_item.html
  31. 660 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/classes/BxBaseModTextTemplate.php
  32. 1 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/template/author.html
  33. 235 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/template/css/main.css
  34. 5 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/template/unit_live_search.html
  35. 1 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/plugins_public/modernizr.min.js
  36. 42 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/searchKeyword.php
  37. BIN
      upgrade/files/10.0.0.B1-10.0.0.B2/files/studio/template/images/icons/ui-location_radius.png
  38. 55 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/_sub_header.html
  39. 17 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/author.html
  40. 3 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/author_desc.html
  41. 24 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/chart_stats.html
  42. 1 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/comment_empty.html
  43. 13 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/comment_reply_box.html
  44. 21 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/create_post_form.html
  45. 163 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/_remixins.less
  46. 32 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/account.css
  47. 786 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/common.css
  48. 700 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/default.less
  49. 485 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/general.css
  50. 34 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/media-desktop.css
  51. 200 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/media-phone.css
  52. 49 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/media-tablet.css
  53. 843 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/menu.css
  54. 10 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/search_extended.css
  55. 169 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/vote.css
  56. 8 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/form_field_location_radius.html
  57. 91 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/grid.html
  58. 11 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/menu_footer.html
  59. 9 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/menu_main_submenu_bar.html
  60. 24 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/menu_profile_stats.html
  61. 48 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/page_22.html
  62. 21 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/profile_stats.html
  63. 13 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/profile_switch_row.html
  64. 1115 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseCmts.php
  65. 393 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseConfig.php
  66. 1741 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseFormView.php
  67. 691 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseGrid.php
  68. 98 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseMenuAccountNotifications.php
  69. 110 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseMenuFooter.php
  70. 168 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseMenuSubmenu.php
  71. 530 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBasePage.php
  72. 95 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBasePageHome.php
  73. 121 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBasePlayerHTML5.php
  74. 168 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseSearchExtendedForm.php
  75. 338 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseServiceConnections.php
  76. 306 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseServiceProfiles.php
  77. 6 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/unit_meta_item.html
  78. 16 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/vote_do_vote_stars.html
  79. 15 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files/template/vote_legend.html
  80. 6 0
      upgrade/files/10.0.0.B1-10.0.0.B2/files_delete.php
  81. 18 0
      upgrade/files/10.0.0.B1-10.0.0.B2/script.php
  82. 7 0
      upgrade/files/10.0.0.B1-10.0.0.B2/sql.sql

+ 1 - 1
inc/version.inc.php

@@ -7,6 +7,6 @@
  * @{
  */
 
-define('BX_DOL_VERSION', '10.0.0-B2'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+define('BX_DOL_VERSION', '10.0.0-DEV2'); ///< 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-B2');
+define ('BX_DOL_VER', '10.0.0-DEV2');
 
 define ('BX_INSTALL_DEFAULT_LANGUAGE', 'en');
 

+ 1 - 1
modules/base/profile/classes/BxBaseModProfileDb.php

@@ -71,7 +71,7 @@ class BxBaseModProfileDb extends BxBaseModGeneralDb
 
         $sQuery = "SELECT `c`.`id` AS `content_id`, `p`.`account_id`, `p`.`id` AS `profile_id`, `p`.`status` AS `profile_status` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` 
             INNER JOIN `sys_profiles` AS `p` ON (`p`.`content_id` = `c`.`id` AND `p`.`type` = :type) 
-            INNER JOIN `sys_accounts` AS `a` ON (`a`.`profile_id` = `p`.`id`) 
+            INNER JOIN `sys_accounts` AS `a` ON (`a`.`id` =  `p`.`account_id`)
             WHERE `p`.`status` = :status AND (0 $sWhere)" . $sOrderBy;
         return $this->getAll($sQuery, $aBindings);
     }

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

+ 90 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolGridQuery.php

@@ -0,0 +1,90 @@
+<?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 grid.
+ * @see BxDolGrid
+ */
+class BxDolGridQuery extends BxDolDb
+{
+    protected $_aObject;
+
+    public function __construct($aObject)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+    }
+
+    static public function getGridObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_grid` WHERE `object` = ?", $sObject);
+        $aObject = $oDb->getRow($sQuery);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        // paginate
+        if (!empty($aObject['paginate_url']) && 0 !== strncasecmp($aObject['paginate_url'], 'http://', 7) && 0 !== strncasecmp($aObject['paginate_url'], 'https://', 8))
+            $aObject['paginate_url'] = BX_DOL_URL_ROOT . $aObject['paginate_url'];
+
+        // filter
+        if ($aObject['filter_fields'])
+            $aObject['filter_fields'] = array_map('trim', explode(',', $aObject['filter_fields']));
+
+        if ($aObject['filter_fields_translatable'])
+            $aObject['filter_fields_translatable'] = array_map('trim', explode(',', $aObject['filter_fields_translatable']));
+
+        // sorting
+        if ($aObject['sorting_fields'])
+            $aObject['sorting_fields'] = array_map('trim', explode(',', $aObject['sorting_fields']));
+
+        if ($aObject['sorting_fields_translatable']) {
+            $aObject['sorting_fields_translatable'] = array_map('trim', explode(',', $aObject['sorting_fields_translatable']));
+            $aObject['sorting_fields'] = array_merge ($aObject['sorting_fields'], $aObject['sorting_fields_translatable']);
+        }
+
+        // get fields
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_grid_fields` WHERE `object` = ? ORDER BY `order`", $sObject);
+        $aFields = $oDb->getAllWithKey($sQuery, 'name');
+        if (!$aFields || !is_array($aFields)) // it is impossible to have grid without any fields
+            return false;
+        foreach ($aFields as $sKey => $aRow) {
+            $aObject['fields'][$sKey] = array(
+                'name' => $sKey,
+                'title' => _t($aRow['title']),
+                'width' => $aRow['width'],
+                'translatable' => $aRow['translatable'],
+                'chars_limit' => $aRow['chars_limit'],
+                'hidden_on' => $aRow['hidden_on'],
+            );
+            if (empty($aRow['params']))
+                continue;
+            $aAdd = unserialize($aRow['params']);
+            if (!empty($aAdd) && is_array($aAdd))
+                $aObject['fields'][$sKey] = array_merge($aObject['fields'][$sKey], $aAdd);
+        }
+        // get actions
+        $a = array('bulk','single','independent');
+        foreach ($a as $sActionType) {
+            $sActionField = 'actions_' . $sActionType;
+            $aObject[$sActionField] = array();
+            $sQuery = $oDb->prepare("SELECT * FROM `sys_grid_actions` WHERE `object` = ? AND `type` = ? ORDER BY `order`", $sObject, $sActionType);
+            $aActions = $oDb->getAllWithKey($sQuery, 'name');
+            if (!$aActions || !is_array($aActions))
+                continue;
+            foreach ($aActions as $sKey => $aRow)
+                $aObject[$sActionField][$sKey] = $aRow['title'] || $aRow['icon'] ? array('title' => _t($aRow['title']), 'icon' => $aRow['icon'], 'confirm' => $aRow['confirm'], 'icon_only' => $aRow['icon_only']) : array();
+        }
+
+        return $aObject;
+    }
+
+}
+
+/** @} */

+ 121 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolMenuQuery.php

@@ -0,0 +1,121 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_MENU_LAST_ITEM_ORDER', 9999);
+
+/**
+ * Database queries for menus.
+ * @see BxDolMenu
+ */
+class BxDolMenuQuery extends BxDolDb
+{
+    protected $_aObject;
+
+    public function __construct($aObject)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+    }
+
+    static public function getMenuObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT `o`.*, `t`.`template` FROM `sys_objects_menu` AS `o` INNER JOIN `sys_menu_templates` AS `t` ON (`t`.`id` = `o`.`template_id`) WHERE `o`.`object` = ?", $sObject);
+        $aObject = $oDb->getRow($sQuery);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        return $aObject;
+    }
+
+    static public function getMenuTriggers($sTriggerName)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_menu_items` WHERE `set_name` = ? ORDER BY `order` DESC", $sTriggerName);
+        return $oDb->getAll($sQuery);
+    }
+
+    static public function addMenuItemToSet($aMenuItem)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        if (empty($aMenuItem['set_name']))
+            return false;
+
+        // check if menu item already exists, 
+        // so the menu item position will not reset when it's unnecessary
+        $sQuery = $oDb->prepare("SELECT `id` FROM `sys_menu_items` WHERE `set_name` = ? AND `name` = ?", $aMenuItem['set_name'], $aMenuItem['name']);
+        if ($oDb->getOne($sQuery))
+            return true;
+      
+        // get order
+        if (empty($aMenuItem['order'])) {
+            $sQuery = $oDb->prepare("SELECT `order` FROM `sys_menu_items` WHERE `set_name` = ? AND `active` = 1 AND `order` != ? ORDER BY `order` DESC LIMIT 1", $aMenuItem['set_name'], BX_MENU_LAST_ITEM_ORDER);
+            $iProfileMenuOrder = (int)$oDb->getOne($sQuery);
+            $aMenuItem['order'] = $iProfileMenuOrder + 1;
+        }
+
+        // add new item
+        unset($aMenuItem['id']);
+        return $oDb->query("INSERT INTO `sys_menu_items` SET " . $oDb->arrayToSQL($aMenuItem));
+    }
+
+    public function getMenuItems()
+    {
+        return $this->getMenuItemsFromSet($this->_aObject['set_name']);
+    }
+
+    public function getMenuItemsFromSet($sSetName)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `sys_menu_items` WHERE `set_name` = ? ORDER BY `order` ASC", $sSetName);
+        return $this->getAllWithKey($sQuery, 'name');
+    }
+
+    public function getMenuItemsBy($aParams = array())
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $aBindings = array();
+
+    	$sSelectClause = '*';
+    	$sWhereClause = $sGroupClause = $sOrderClause = '';
+    	$sLimitClause = isset($aParams['start']) && !empty($aParams['per_page']) ? " LIMIT " . $aParams['start'] . ", " . $aParams['per_page'] : "";
+
+    	if(!empty($aParams['type']))
+            switch($aParams['type']) {
+                case 'set_name':
+                    $aBindings['set_name'] = $aParams['set_name'];
+
+                    $sWhereClause = 'AND `set_name` = :set_name';
+                    $sOrderClause = '`order` ASC';
+                    break;
+                    
+                case 'set_name_duplicates':
+                    $aMethod['name'] = 'getColumn';
+                    $aBindings['set_name'] = $aParams['set_name'];
+
+                    $sSelectClause = '`name`';
+                    $sWhereClause = 'AND `set_name` = :set_name';
+                    $sGroupClause = '`name` HAVING COUNT(`id`) > 1';
+                    break;
+            }
+
+        if(!empty($sGroupClause))
+            $sGroupClause = " GROUP BY " . $sGroupClause;
+
+        if(!empty($sOrderClause))
+            $sOrderClause = " ORDER BY " . $sOrderClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `sys_menu_items` WHERE 1 " . $sWhereClause . $sGroupClause . $sOrderClause . $sLimitClause;
+        $aMethod['params'][] = $aBindings;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+}
+
+/** @} */

+ 154 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolMetatagsQuery.php

@@ -0,0 +1,154 @@
+<?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 metatags objects.
+ * @see BxDolMetatag
+ */
+class BxDolMetatagsQuery extends BxDolDb
+{
+    protected $_aObject;
+
+    public function __construct($aObject)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+    }
+
+    static public function getMetatagsObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_metatags` WHERE `object` = ?", $sObject);
+        $aObject = $oDb->getRow($sQuery);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        return $aObject;
+    }
+    
+    public function mentionsAdd($mixedContentId, $aMentions, $bDeletePrevious = true)
+    {
+        return $this->metaAdd($mixedContentId, $aMentions, $bDeletePrevious, 'mentionsDelete', $this->_aObject['table_mentions'], 'profile_id');
+    }
+
+    public function mentionsDelete($mixedContentId)
+    {
+        return $this->metaDelete($this->_aObject['table_mentions'], $mixedContentId);
+    }
+
+    public function mentionsGet($mixedContentId)
+    {
+        $sQuery = $this->prepare("SELECT `profile_id` FROM `{$this->_aObject['table_mentions']}` WHERE `object_id` = ? ORDER BY `profile_id` DESC LIMIT ?", $mixedContentId, (int)getParam('sys_metatags_mentions_max'));
+        return $this->getColumn($sQuery);
+    }
+    
+    public function keywordsAdd($mixedContentId, $aKeywords, $bDeletePreviousKeywords = true)
+    {
+        return $this->metaAdd($mixedContentId, $aKeywords, $bDeletePreviousKeywords, 'keywordsDelete', $this->_aObject['table_keywords'], 'keyword');
+    }
+
+    public function keywordsDelete($mixedContentId)
+    {
+        $mixedResult = $this->metaDelete($this->_aObject['table_keywords'], $mixedContentId);
+        if ($mixedResult != false)
+            bx_alert('meta_keyword', 'deleted', $mixedContentId, bx_get_logged_profile_id(), array('object' => $this->_aObject['object']));
+        return $mixedResult;
+    }
+
+    public function keywordsGet($mixedContentId)
+    {
+        $sQuery = $this->prepare("SELECT `keyword` FROM `{$this->_aObject['table_keywords']}` WHERE `object_id` = ? ORDER BY CHAR_LENGTH(`keyword`) DESC LIMIT ?", $mixedContentId, (int)getParam('sys_metatags_hashtags_max'));
+        return $this->getColumn($sQuery);
+    }
+
+    public function keywordsGetSQLParts($sContentTable, $sContentField, $mixedKeyword)
+    {
+        if(!is_array($mixedKeyword))
+            $mixedKeyword = array($mixedKeyword);
+
+        return array(
+            'where' => !empty($mixedKeyword) ? ' AND `tt`.`keyword` IN (' . $this->implode_escape($mixedKeyword) . ')' : '',
+            'join' => 'INNER JOIN `' . $this->_aObject['table_keywords'] . '` AS `tt` ON `' . $sContentTable . '`.`' . $sContentField . '`=`tt`.`object_id`'
+        );
+    }
+
+    public function keywordsPopularList($iLimit)
+    {
+        $sQuery = $this->prepare("SELECT `keyword`, COUNT(*) as `count` FROM `{$this->_aObject['table_keywords']}` GROUP BY `keyword` ORDER BY `count` DESC LIMIT ?", $iLimit);
+        return $this->getPairs($sQuery, 'keyword', 'count');
+    }    
+
+
+
+    public function locationsAdd($mixedContentId, $fLat, $fLng, $sCountryCode, $sState, $sCity, $sZip, $sStreet, $sStreetNumber)
+    {
+        $this->locationsDelete($mixedContentId);
+        if (!$fLat && !$fLng)
+            return true;
+
+        $sQuery = $this->prepare("INSERT INTO `{$this->_aObject['table_locations']}` SET `object_id` = ?, `lat` = ?, `lng` = ?, `country` = ?, `state` = ?, `city` = ?, `zip` = ?, `street` = ?, `street_number` = ?", $mixedContentId, $fLat, $fLng, $sCountryCode, $sState, $sCity, $sZip, $sStreet, $sStreetNumber);
+        return $this->query($sQuery);
+    }
+    
+    public function locationsDelete($mixedContentId)
+    {
+        return $this->metaDelete($this->_aObject['table_locations'], $mixedContentId);
+    }
+
+    public function locationGet($mixedContentId)
+    {
+        $sQuery = $this->prepare("SELECT * FROM `{$this->_aObject['table_locations']}` WHERE `object_id` = ?", $mixedContentId);
+        return $this->getRow($sQuery);
+    }
+
+    public function locationsGetSQLParts($sContentTable, $sContentField, $sCountry = false, $sState = false, $sCity = false, $sZip = false, $aBounds = array())
+    {
+        $aFields = array('country' => 'sCountry', 'state' => 'sState', 'city' => 'sCity', 'zip' => 'sZip', 'bounds' => 'aBounds');
+
+        $aWhere = array();
+        $sWhereBounds = '';
+        foreach ($aFields as $sIndex => $sVar) {
+            if (!$$sVar)
+                continue;
+
+            if ('bounds' == $sIndex) {
+                $sWhereBounds = $this->prepareAsString(" AND `tl`.`lat` != 0 AND `tl`.`lng` != 0 AND `tl`.`lat` > ? AND `tl`.`lat` < ? AND `tl`.`lng` > ? AND `tl`.`lng` < ?", ${$sVar}['min_lat'], ${$sVar}['max_lat'], ${$sVar}['min_lng'], ${$sVar}['max_lng']);
+            } 
+            else {
+                $aWhere['tl`.`' . $sIndex] = $$sVar;
+            }
+        }
+
+        return array(
+            'where' => $sWhereBounds . (!empty($aWhere) ? ' AND ' . $this->arrayToSQL($aWhere, ' AND ') : ''),
+            'join' => 'INNER JOIN `' . $this->_aObject['table_locations'] . '` AS `tl` ON `' . $sContentTable . '`.`' . $sContentField . '`=`tl`.`object_id`'
+        );
+    }
+
+    protected function metaDelete($sTable, $mixedContentId)
+    {
+        $sQuery = $this->prepare("DELETE FROM `{$sTable}` WHERE `object_id` = ?", $mixedContentId);
+        return $this->query($sQuery);
+    }
+
+    protected function metaAdd($mixedContentId, $aMetas, $bDeletePrevious, $sFuncDelete, $sTable, $sField)
+    {
+        if ($bDeletePrevious)
+            $this->$sFuncDelete($mixedContentId);
+
+        $i = 0;
+        foreach ($aMetas as $sMeta) {
+            $sQuery = $this->prepare("INSERT INTO `{$sTable}` SET `object_id` = ?, `{$sField}` = ?", $mixedContentId, $sMeta);
+            $i += ($this->query($sQuery) ? 1 : 0);
+        }
+        return $i;
+    }
+}
+
+/** @} */

+ 212 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolModuleQuery.php

@@ -0,0 +1,212 @@
+<?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 BxDolModuleQuery 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()
+    {
+        $sClass = __CLASS__;
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new $sClass();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    function getModuleById($iId, $bFromCache = true)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_modules` WHERE `id`=? LIMIT 1", $iId);
+        return $bFromCache ? $this->fromMemory('sys_modules_' . $iId, 'getRow', $sSql) : $this->getRow($sSql);
+    }
+    function getModuleByName($sName, $bFromCache = true)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_modules` WHERE `name`=? LIMIT 1", $sName);
+        return $bFromCache ? $this->fromMemory('sys_modules_' . $sName, 'getRow', $sSql) : $this->getRow($sSql);
+    }
+    function getModuleByUri($sUri, $bFromCache = true)
+    {
+        $sSql = $this->prepare("SELECT * FROM `sys_modules` WHERE `uri`=? LIMIT 1", $sUri);
+        return $bFromCache ? $this->fromMemory('sys_modules_' . $sUri, 'getRow', $sSql) : $this->getRow($sSql);
+    }
+    function enableModuleByUri($sUri)
+    {
+        $sSql = $this->prepare("UPDATE `sys_modules` SET `enabled`='1' WHERE `uri`=? LIMIT 1", $sUri);
+        return (int)$this->query($sSql) > 0;
+    }
+    function disableModuleByUri($sUri)
+    {
+        $sSql = $this->prepare("UPDATE `sys_modules` SET `enabled`='0' WHERE `uri`=? LIMIT 1", $sUri);
+        return (int)$this->query($sSql) > 0;
+    }
+    function setModulePendingUninstall($sUri, $bPendingUninstall)
+    {
+        $sSql = $this->prepare("UPDATE `sys_modules` SET `pending_uninstall` = ? WHERE `uri` = ? LIMIT 1", $bPendingUninstall ? 1 : 0, $sUri);
+        return $this->query($sSql);
+    }
+    function isModule($sUri)
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_modules` WHERE `uri`=? LIMIT 1", $sUri);
+        return (int)$this->getOne($sSql) > 0;
+    }
+    function isModuleByName($sName)
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_modules` WHERE `name`=? LIMIT 1", $sName);
+        return (int)$this->getOne($sSql) > 0;
+    }
+    function isModuleParamsUsed($sUri, $sPath, $sPrefixDb, $sPrefixClass)
+    {
+        $sSql = "SELECT `id` FROM `sys_modules` WHERE `uri`='" . $sUri . "' || `path`='" . $sPath . "' || `db_prefix`='" . $sPrefixDb . "' || `class_prefix`='" . $sPrefixClass . "' LIMIT 1";
+        return (int)$this->getOne($sSql) > 0;
+    }
+    function isEnabled($sUri)
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_modules` WHERE `uri`=? AND `enabled`='1' LIMIT 1", $sUri);
+        return (int)$this->getOne($sSql) > 0;
+    }
+    function isEnabledByName($sName)
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_modules` WHERE `name`=? AND `enabled`='1' LIMIT 1", $sName);
+        return (int)$this->getOne($sSql) > 0;
+    }
+    function getModules()
+    {
+        $sSql = "SELECT * FROM `sys_modules` ORDER BY `title`";
+        return $this->fromMemory('sys_modules', 'getAll', $sSql);
+    }
+    function getModulesBy($aParams = array(), $bFromCache = true)
+    {
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sPostfix = $sWhereClause = $sOrderByClause = "";
+        $aBindings = array();
+
+        switch($aParams['type']) {
+            case 'type':
+                if(!is_array($aParams['value']))
+                    $aParams['value'] = array($aParams['value']);
+
+                $sPostfix .= '_type_' . implode('_', $aParams['value']);
+                $sWhereClause .= " AND `type` IN (" . $this->implode_escape($aParams['value']) . ")";
+                break;
+
+            case 'modules':
+                $sPostfix .= '_modules';
+                $aBindings['type'] = BX_DOL_MODULE_TYPE_MODULE;
+
+                $sWhereClause .= " AND `type`=:type";
+                break;
+
+            case 'languages':
+                $sPostfix .= '_languages';
+                $aBindings['type'] = BX_DOL_MODULE_TYPE_LANGUAGE;
+
+                $sWhereClause .= " AND `type`=:type";
+                break;
+
+            case 'templates':
+                $sPostfix .= '_templates';
+                $aBindings['type'] = BX_DOL_MODULE_TYPE_TEMPLATE;
+
+                $sWhereClause .= " AND `type`=:type";
+                break;
+
+            case 'path_and_uri':
+            	$aMethod['name'] = 'getRow';
+            	$aBindings = array_merge($aBindings, array(
+                    'path' => $aParams['path'],
+                    'uri' => $aParams['uri']
+                ));
+
+            	$sWhereClause .= " AND `path`=:path AND `uri`=:uri";
+            	break;
+
+            case 'all_pairs_name_uri':
+            	$aMethod['name'] = 'getPairs';
+            	$aMethod['params'][1] = 'name';
+            	$aMethod['params'][2] = 'uri';
+            	break;
+
+            case 'all':
+            	break;
+        }
+
+        if(isset($aParams['active'])) {
+            $sPostfix .= "_active";
+            $aBindings['enabled'] = (int)$aParams['active'];
+
+            $sWhereClause .= " AND `enabled`=:enabled";
+        }
+
+        $sOrderByClause = " ORDER BY " . (isset($aParams['order_by']) ? $aParams['order_by'] : '`title`');
+
+        $aMethod['params'][0] = "SELECT
+                `id`,
+                `type`,
+                `name`,
+                `title`,
+                `vendor`,
+                `version`,
+                `help_url`,
+                `path`,
+                `uri`,
+                `class_prefix`,
+                `db_prefix`,
+                `lang_category`,
+                `date`,
+                `enabled`
+            FROM `sys_modules`
+            WHERE 1 " . $sWhereClause . $sOrderByClause;
+        $aMethod['params'][] = $aBindings;
+
+        if(!$bFromCache || empty($sPostfix))
+        	return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        return call_user_func_array(array($this, 'fromMemory'), array_merge(array('sys_modules' . $sPostfix, $aMethod['name']), $aMethod['params']));
+    }
+
+    function getModulesUri()
+    {
+        return $this->fromMemory('sys_modules_uri', 'getColumn', 'SELECT `uri` FROM `sys_modules` ORDER BY `uri`');
+    }
+
+    function getDependent($sName, $sUri)
+    {
+        return $this->getAll("SELECT `id`, `title`, `enabled` FROM `sys_modules` WHERE (`dependencies` LIKE " . $this->escape('%' . $sName . '%') . " OR `dependencies` LIKE " . $this->escape('%' . $sUri . '%') . ") AND `enabled`='1'");
+    }
+
+    public function updateModule($aParamsSet, $aParamsWhere = array())
+    {
+        if(empty($aParamsSet))
+            return false;
+
+        $sWhereClause = !empty($aParamsWhere) ? $this->arrayToSQL($aParamsWhere, " AND ") : "1";
+
+        $sSql = "UPDATE `sys_modules` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $sWhereClause;
+        return $this->query($sSql);
+    }
+}
+
+/** @} */

+ 229 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolObjectQuery.php

@@ -0,0 +1,229 @@
+<?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 BxDolObject
+ */
+class BxDolObjectQuery extends BxDolDb
+{
+    protected $_oModule;
+
+    protected $_sTable;
+
+    protected $_sTableTrack;
+    protected $_sTableTrackFieldAuthor;
+
+    protected $_sTriggerTable;
+    protected $_sTriggerFieldId;
+    protected $_sTriggerFieldAuthor;
+    protected $_sTriggerFieldCount;
+
+    protected $_sMethodGetEntry;
+
+    public function __construct(&$oModule)
+    {
+        parent::__construct();
+
+        $this->_oModule = $oModule;
+
+        $aSystem = $this->_oModule->getSystemInfo();
+        $this->_sTable = isset($aSystem['table_main']) ? $aSystem['table_main'] : '';
+
+        $this->_sTableTrack = isset($aSystem['table_track']) ? $aSystem['table_track'] : '';
+        $this->_sTableTrackFieldAuthor = 'author_id';
+        
+        $this->_sTriggerTable = isset($aSystem['trigger_table']) ? $aSystem['trigger_table'] : '';
+        $this->_sTriggerFieldId = isset($aSystem['trigger_field_id']) ? $aSystem['trigger_field_id'] : '';
+        $this->_sTriggerFieldAuthor = isset($aSystem['trigger_field_author']) ? $aSystem['trigger_field_author'] : '';
+        $this->_sTriggerFieldCount = isset($aSystem['trigger_field_count']) ? $aSystem['trigger_field_count'] : '';
+    }
+
+    /**
+     * Get SQL parts for main table. 
+     */
+    public function getSqlParts($sMainTable, $sMainField)
+    {
+        if(empty($this->_sTable) || empty($sMainTable) || empty($sMainField))
+            return array();
+
+        return array (
+            'fields' => ", `{$this->_sTable}`.`count` as `count` ",
+            'join' => " LEFT JOIN `{$this->_sTable}` ON (`{$this->_sTable}`.`object_id` = `{$sMainTable}`.`{$sMainField}`) ",
+        );
+    }
+
+    /**
+     * Get SQL parts for track table. 
+     */
+    public function getSqlPartsTrack($sMainTable, $sMainField, $iAuthorId = 0)
+    {
+        if(empty($this->_sTableTrack) || empty($sMainTable) || empty($sMainField))
+            return array();
+
+        return array (
+            'fields' => ", `{$this->_sTableTrack}`.`author_id` as `favorer_id` ",
+            'where' => $this->prepareAsString(" AND `{$this->_sTableTrack}`.`author_id` = ?", $iAuthorId),
+            'join' => " LEFT JOIN `{$this->_sTableTrack}` ON (`{$this->_sTableTrack}`.`object_id` = `{$sMainTable}`.`{$sMainField}`) ",
+        );
+    }
+
+    public function isPerformed($iObjectId, $iAuthorId)
+    {
+        $sQuery = $this->prepare("SELECT `object_id` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+        return (int)$this->getOne($sQuery) != 0;
+    }
+
+    public function getPerformedBy($iObjectId)
+    {
+        $sQuery = $this->prepare("SELECT `author_id` FROM `{$this->_sTableTrack}` WHERE `object_id`=?", $iObjectId);
+        return $this->getColumn($sQuery);
+    }
+
+    public function getTrack($iObjectId, $iAuthorId)
+    {
+        return $this->getRow("SELECT * FROM `{$this->_sTableTrack}` WHERE `object_id` = :object_id AND `author_id` = :author_id LIMIT 1", array(
+            'object_id' => $iObjectId,
+            'author_id' => $iAuthorId
+        ));
+    }
+    
+    public function getTrackBy($aParams = array())
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sWhereClause = $sOrderByClause = $sLimitClause = "";
+        $aBindings = array();
+
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aBindings['id'] = $aParams['id'];
+
+                $sWhereClause .= " AND `id`=:id";
+                break;
+
+            case 'all':
+            	break;
+        }
+
+        if(!empty($aParams['order_by']))
+            $sOrderByClause = " ORDER BY " . $aParams['order_by'] . (!empty($aParams['order_way']) ? " " . $aParams['order_way'] : "");
+
+        if(isset($aParams['start']) && !empty($aParams['per_page']))
+            $sLimitClause = " LIMIT " . $aParams['start'] . ", " . $aParams['per_page'];
+
+        $aMethod['params'][0] = "SELECT * FROM `{$this->_sTableTrack}` WHERE 1 " . $sWhereClause . $sOrderByClause . $sLimitClause;
+        $aMethod['params'][] = $aBindings;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function deleteObjectEntries($iObjectId)
+    {
+    	if(!empty($this->_sTable)) {
+            $sQuery = $this->prepare("DELETE FROM `{$this->_sTable}` WHERE `object_id` = ?", $iObjectId);
+            if($this->query($sQuery))
+                $this->query("OPTIMIZE TABLE `{$this->_sTable}`");
+    	}
+
+    	if(!empty($this->_sTableTrack)) {
+            $sQuery = $this->prepare("DELETE FROM `{$this->_sTableTrack}` WHERE `object_id` = ?", $iObjectId);
+            if($this->query($sQuery))
+                $this->query ("OPTIMIZE TABLE `{$this->_sTableTrack}`");
+    	}
+    }
+    public function deleteAuthorEntries($iAuthorId)
+    {
+        if(empty($this->_sTableTrack))
+            return;
+
+        $bTable = !empty($this->_sTable);
+        $bTableTrigger = !empty($this->_sTriggerTable) && !empty($this->_sTriggerFieldCount);
+
+        if($bTable || $bTableTrigger) {
+            $aTracks = $this->getAll("SELECT * FROM `{$this->_sTableTrack}` WHERE `{$this->_sTableTrackFieldAuthor}`=:author_id", array('author_id' => $iAuthorId));
+            foreach($aTracks as $aTrack) {
+                if($bTable)
+                    $this->_deleteAuthorEntriesTableMain($aTrack);
+    
+                /**
+                 * Note. It's essential that Trigger Table is updated at the end, 
+                 * because it may require updated data from main ($this->_sTable) table. 
+                 */
+                if($bTableTrigger)
+                    $this->_deleteAuthorEntriesTableTrigger($aTrack);
+            }
+        }
+
+        $sQuery = $this->prepare("DELETE FROM `{$this->_sTableTrack}` WHERE `{$this->_sTableTrackFieldAuthor}`=?", $iAuthorId);
+        if($this->query($sQuery))
+            $this->query("OPTIMIZE TABLE `{$this->_sTableTrack}`");
+    }
+
+    public function getObjectAuthorId($iId)
+    {
+        if(empty($this->_sTriggerFieldAuthor))
+            return 0;
+
+        $sQuery = $this->prepare("SELECT `{$this->_sTriggerFieldAuthor}` FROM `{$this->_sTriggerTable}` WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iId);
+        return (int)$this->getOne($sQuery);
+    }
+
+    public function getObjectCount($iId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->_sTriggerFieldCount}` FROM `{$this->_sTriggerTable}` WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iId);
+        return (int)$this->getOne($sQuery);
+    }
+
+    public function updateMainTableValue($iObjectId, $iValue)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `count` = `count` + ? WHERE `object_id` = ?", (int)$iValue, $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function updateTriggerTable($iObjectId)
+    {
+    	if(empty($this->_sMethodGetEntry))
+            return false;
+
+        $aEntry = $this->{$this->_sMethodGetEntry}($iObjectId);
+        if(empty($aEntry) || !is_array($aEntry))
+            return false;
+
+        return $this->_updateTriggerTable($iObjectId, $aEntry);
+    }
+
+    /**
+     * Is used instead of 'updateTriggerTable' when trigger table contains 
+     * only one field related to this object (View, Vote, Comment, etc).
+     * For example, it's used in Favourite and Feature objects.
+     */
+    public function updateTriggerTableValue($iObjectId, $iValue)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldCount}` = `{$this->_sTriggerFieldCount}` + ? WHERE `{$this->_sTriggerFieldId}` = ?", (int)$iValue, $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    protected function _updateTriggerTable($iObjectId, $aEntry)
+    {
+    	$sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldCount}` = ? WHERE `{$this->_sTriggerFieldId}` = ?", $aEntry['count'], $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    protected function _deleteAuthorEntriesTableMain($aTrack)
+    {
+        return $this->updateMainTableValue($aTrack['object_id'], -1);
+    }
+
+    protected function _deleteAuthorEntriesTableTrigger($aTrack)
+    {
+        return $this->updateTriggerTableValue($aTrack['object_id'], -1);
+    }
+}
+
+/** @} */

+ 277 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolRelation.php

@@ -0,0 +1,277 @@
+<?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 BxDolRelation extends BxDolConnection
+{
+    protected $_sParamDivider;
+    protected $_sParamEnabled;
+
+    protected $_sPreList;
+
+    protected function __construct($aObject)
+    {
+        parent::__construct($aObject);
+
+        $this->_oQuery = new BxDolRelationQuery($aObject);
+
+        $this->_sParamDivider = '_';
+        $this->_sParamEnabled = 'sys_relations';
+        $this->_sPreList = 'sys_relations';
+    }
+
+    /**
+     * Add new relation.
+     * @param $mixedContent content to make relation with or an array with content and relation type
+     * @return array
+     */
+    public function actionAdd($mixedContent = 0, $iInitiator = false)
+    {
+        if(empty($mixedContent))
+            $mixedContent = bx_process_input($_POST['id'], BX_DATA_INT);
+
+        $iContent = 0;
+        $iRelation = 0;
+        if(is_array($mixedContent)) {
+            $iContent = (int)$mixedContent['content'];
+            $iRelation = (int)$mixedContent['relation'];
+        }
+        else
+            $iContent = (int)$mixedContent;
+
+        $iInitiator = $iInitiator ? (int)$iInitiator : (int)bx_get_logged_profile_id();
+
+        $aResult = parent::actionAdd($iContent, $iInitiator);
+        if(empty($iRelation) || (isset($aResult['err']) && $aResult['err'] !== false))
+            return $aResult;
+
+        $this->_oQuery->updateConnection($iInitiator, $iContent, array(
+            'relation' => $iRelation
+        ));
+
+        return $aResult;
+    }
+    
+    /**
+     * Confirm relation request without creation of retroactive relation.
+     * @param $iContent content to make relation with
+     * @return array
+     */
+    public function actionConfirm($iContent = 0, $iInitiator = false)
+    {
+        if(!$iContent)
+            $iContent = bx_process_input($_POST['id'], BX_DATA_INT);
+
+        return $this->_action($iContent, $iInitiator ? $iInitiator : bx_get_logged_profile_id(), 'confirmConnection', '_sys_conn_err_connection_does_not_exists');
+    }
+
+    /**
+     * Remove relation without removing a retroactive relation. This method is wrapper for @see removeConnection to be called from @see conn.php upon AJAX request to this file.
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return array
+     */
+    public function actionRemove($iContent = 0, $iInitiator = false)
+    {
+        if(!$iContent)
+            $iContent = bx_process_input($_POST['id'], BX_DATA_INT);
+
+        return $this->_action($iInitiator ? $iInitiator : bx_get_logged_profile_id(), $iContent, 'removeConnection', '_sys_conn_err_connection_does_not_exists');
+    }
+
+    /**
+     * Confirm relation request without creation of retroactive relation.
+     * @param $iInitiator initiator of the connection, in most cases some profile id
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return true - if connection was added, false - if connection already exists or error occured
+     */
+    public function confirmConnection($iInitiator, $iContent)
+    {
+        $iMutual = 1;
+        if(!$this->_oQuery->updateConnectionMutual((int)$iInitiator, (int)$iContent, $iMutual))
+            return false;
+
+        bx_alert($this->_sObject, 'connection_confirmed', 0, bx_get_logged_profile_id(), array(
+            'initiator' => (int)$iInitiator,
+            'content' => (int)$iContent,
+            'mutual' => (int)$iMutual,
+            'object' => $this,
+        ));
+
+        return true;
+    }
+
+    /**
+     * Compound function, which calls getCommonContentExt, getConnectedContentExt or getConnectedInitiatorsExt depending on $sContentType
+     * @param $sContentType content type to get BX_CONNECTIONS_CONTENT_TYPE_CONTENT, BX_CONNECTIONS_CONTENT_TYPE_INITIATORS or BX_CONNECTIONS_CONTENT_TYPE_COMMON
+     * @param $iId1 one content or initiator
+     * @param $iId2 second content or initiator only in case of BX_CONNECTIONS_CONTENT_TYPE_COMMON content type
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectionsAsArrayExt($sContentType, $iId1, $iId2, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        if (BX_CONNECTIONS_CONTENT_TYPE_COMMON == $sContentType)
+            return $this->getCommonContentExt($iId1, $iId2, $isMutual, $iStart, $iLimit, $iOrder);
+
+        if (BX_CONNECTIONS_CONTENT_TYPE_INITIATORS == $sContentType)
+            $sMethod = 'getConnectedInitiatorsExt';
+        else
+            $sMethod = 'getConnectedContentExt';
+
+        return $this->$sMethod($iId1, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get common content (full info) between two initiators
+     * @param $iInitiator1 one initiator
+     * @param $iInitiator2 second initiator
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getCommonContentExt($iInitiator1, $iInitiator2, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getCommonContentExt($iInitiator1, $iInitiator2, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get connected initiators (full info)
+     * @param $iContent content of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectedInitiatorsExt($iContent, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getConnectedInitiatorsExt($iContent, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get connected content (full info)
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */
+    public function getConnectedContentExt($iInitiator, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE)
+    {
+        return $this->_oQuery->getConnectedContentExt($iInitiator, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Check whether connection between Initiator and Content can be established.
+     */
+    public function checkAllowedConnect($iInitiator, $iContent, $isPerformAction = false, $isMutual = false, $isInvertResult = false, $isSwap = false)
+    {
+        if(!$this->isRelationAvailable($iInitiator, $iContent))
+            return _t('_sys_txt_access_denied');
+
+        $mixedResult = $this->checkAllowedConnectCustom($iInitiator, $iContent, $isPerformAction, $isMutual, $isInvertResult, $isSwap);
+        if($mixedResult !== CHECK_ACTION_RESULT_ALLOWED)
+            return $mixedResult;
+
+        return parent::checkAllowedConnect($iInitiator, $iContent, $isPerformAction, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    /**
+     * Custom check action method which can be overwritten.
+     * Currently only friends can establish relations.
+     */
+    public function checkAllowedConnectCustom($iInitiator, $iContent, $isPerformAction = false, $isMutual = false, $isInvertResult = false, $isSwap = false)
+    {
+        if(!BxDolConnection::getObjectInstance('sys_profiles_friends')->isConnected($iInitiator, $iContent, true))
+            return _t('_sys_txt_access_denied');
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function isRelationAvailableFromProfile($sModule)
+    {
+        $sModule .= $this->_sParamDivider;
+
+        $aTypes = $this->getRelationTypes();
+        foreach($aTypes as $sType)
+            if(substr($sType, 0, strlen($sModule)) == $sModule)
+                return true;
+
+        return false;
+    }
+
+    public function isRelationAvailableWithProfile($sModule)
+    {
+        $sModule = $this->_sParamDivider . $sModule;
+
+        $aTypes = $this->getRelationTypes();
+        foreach($aTypes as $sType)
+            if(substr($sType, -strlen($sModule)) == $sModule)
+                return true;
+
+        return false;
+    }
+    
+    public function isRelationAvailableBetweenProfiles($sModuleInitiator, $sModuleContent)
+    {
+        $aTypes = $this->getRelationTypes();
+        if(in_array($sModuleInitiator . $this->_sParamDivider . $sModuleContent, $aTypes))
+            return true;
+
+        return false;
+    }
+
+    public function isRelationAvailable($iInitiator, $iContent)
+    {
+        $oInitiator = BxDolProfile::getInstance($iInitiator);
+        $oContent = BxDolProfile::getInstance($iContent);
+        if(!$oInitiator || !$oContent)
+            return false;
+
+        return $this->isRelationAvailableBetweenProfiles($oInitiator->getModule(), $oContent->getModule());
+    }
+
+    public function getRelations($iInitiator, $iContent, &$aSuggestions = array())
+    {
+        $aRelations = BxDolFormQuery::getDataItems($this->_sPreList, false, BX_DATA_VALUES_ALL);
+
+        bx_alert($this->_sObject, 'get_relations', 0, bx_get_logged_profile_id(), array(
+            'initiator' => (int)$iInitiator,
+            'content' => (int)$iContent,
+            'pre_list' => $this->_sPreList,
+            'relations' => &$aRelations
+        ));
+
+        if($this->isConnected($iContent, $iInitiator)) {
+            $iRelation = $this->getRelation($iContent, $iInitiator);
+            if(!empty($iRelation) && !empty($aRelations[$iRelation]['Data']))
+                $aSuggestions = unserialize($aRelations[$iRelation]['Data']);
+        }
+
+        return $aRelations;
+    }
+
+    public function getRelation($iInitiator, $iContent)
+    {
+        $aConnection = $this->_oQuery->getConnection ($iInitiator, $iContent);
+        if(empty($aConnection) || !is_array($aConnection))
+            return 0;
+
+        return (int)$aConnection['relation'];
+    }
+
+    public function getRelationTranslation($iValue, $sUseValues = BX_DATA_VALUES_DEFAULT)
+    {
+        $aRelations = BxDolFormQuery::getDataItems($this->_sPreList, false, $sUseValues);
+
+        return !empty($aRelations[$iValue]) ? $aRelations[$iValue] : _t('_uknown');
+    }
+
+    public function getRelationTypes()
+    {
+        $sParam = getParam($this->_sParamEnabled);
+        if(empty($sParam))
+            return array();
+
+        return explode(',', $sParam);
+    }
+}

+ 128 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolSearchExtended.php

@@ -0,0 +1,128 @@
+<?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('BxDolForm');
+
+class BxDolSearchExtended extends BxDolFactory implements iBxDolFactoryObject
+{
+    public static $SEARCHABLE_TYPES = array(
+        'text', 'textarea', 'number', 
+        'select', 'radio_set',
+        'checkbox_set', 'select_multiple',
+        'checkbox', 'switcher',
+        'datepicker', 'datetime',
+        'location', 'location_radius'
+    );
+
+    public static $TYPE_TO_TYPE_SEARCH = array(
+        'text' => array('text'),
+        'textarea' => array('text'),
+        'number' => array('text'),
+        'text_auto' => array('text_auto'),
+        'select' => array('checkbox_set', 'select_multiple', 'select'),
+        'radio_set' => array('checkbox_set', 'select_multiple', 'select'),
+        'checkbox_set' => array('checkbox_set', 'select_multiple', 'select'),
+        'select_multiple' => array('checkbox_set', 'select_multiple', 'select'),
+        'checkbox' => array('checkbox', 'switcher'),
+        'switcher' => array('checkbox', 'switcher'),
+        'datepicker' => array('datepicker_range', 'datepicker_range_age'),
+        'datetime' => array('datetime_range', 'datetime_range_age'),
+        'location' => array('location', 'location_radius')
+    );
+
+    public static $TYPE_TO_OPERATOR = array(
+        'text' => array('like', '='),
+        'textarea' => array('like', '='),
+        'number' => array('='),
+        'text_auto' => array('in'),
+        'select' => array('in'),
+        'radio_set' => array('in'),
+        'checkbox_set' => array('and'), 
+        'select_multiple' => array('and'),
+        'checkbox' => array('='),
+        'switcher' => array('='),
+    	'datepicker' => array('between'), 
+    	'datetime' => array('between'),
+        'location' => array('locate')
+    );
+
+    protected $_oDb;
+
+    protected $_sObject;
+    protected $_aObject;
+
+
+    /**
+     * Constructor
+     * @param $aObject array of search options
+     */
+    protected function __construct($aObject)
+    {
+        parent::__construct();
+
+        $this->_oDb = new BxDolSearchExtendedQuery($this->_aObject);
+
+        $this->_sObject = $aObject['object'];
+        $this->_aObject = $aObject;
+    }
+
+    /**
+     * Get editor object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject, $oTemplate = false)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolSearchExtended!' . $sObject]))
+            return $GLOBALS['bxDolClasses']['BxDolSearchExtended!' . $sObject];
+
+        $aObject = BxDolSearchExtendedQuery::getSearchObject($sObject);
+        if(!$aObject || !is_array($aObject))
+            return false;
+
+        $sClass = 'BxTemplSearchExtended';
+        if(!empty($aObject['class_name'])) {
+            $sClass = $aObject['class_name'];
+            if(!empty($aObject['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aObject['class_file']);
+        }
+
+        $o = new $sClass($aObject, $oTemplate);
+        return ($GLOBALS['bxDolClasses']['BxDolSearchExtended!' . $sObject] = $o);
+    }
+
+    static public function actionGetAuthors()
+    {
+        $aResult = BxDolService::call('system', 'profiles_search', array(bx_get('term')), 'TemplServiceProfiles');
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo json_encode($aResult);
+    }
+
+    public function isEnabled()
+    {
+        return isset($this->_aObject['active']) && (int)$this->_aObject['active'] != 0;
+    }
+
+    public function clean()
+    {
+        return $this->_oDb->deleteFields(array('object' => $this->_sObject)) !== false;
+    }
+
+    public function reset()
+    {
+        if(!$this->clean())
+            return false;
+
+        $this->_aObject['fields'] = BxDolSearchExtendedQuery::getSearchFields($this->_aObject);
+        return true;
+    }
+}
+
+/** @} */

+ 148 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolSearchExtendedQuery.php

@@ -0,0 +1,148 @@
+<?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 BxDolSearchExtendedQuery extends BxDolDb
+{
+    public function __construct($aObject = array())
+    {
+        parent::__construct();
+    }
+
+    static public function getSearchObject($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_search_extended` WHERE `object` = ?", $sObject);
+
+        $aObject = $oDb->getRow($sQuery);
+        if(!$aObject || !is_array($aObject))
+            return false;
+
+        $aObject['fields'] = self::getSearchFields($aObject);
+        return $aObject;
+    }
+
+    static public function getSearchFields($aObject)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $sQueryFields = "SELECT * FROM `sys_search_extended_fields` WHERE `object` = :object ORDER BY `order`";
+        $aQueryFieldsBindings = array('object' => $aObject['object']);
+        $aFields = $oDb->getAll($sQueryFields, $aQueryFieldsBindings);
+
+        //--- Get fields
+        if(empty($aFields) || !is_array($aFields)) {
+            $aFields = BxDolContentInfo::getObjectInstance($aObject['object_content_info'])->getSearchableFieldsExtended();
+            if(empty($aFields) || !is_array($aFields))
+                return array();
+
+            $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+            $aLangIds = array_keys($oLanguage->getLanguages(true));
+
+            $iOrder = 0;
+            foreach($aFields as $sField => $aField) {
+                $iNow = time();
+                $sCaptionKey = (!empty($aField['caption']) ? $aField['caption'] : '_sys_form_input_' . $sField) . '_' . $iNow;
+                $sInfoKey = (!empty($aField['info']) ? $aField['info'] : '_sys_form_input_' . $sField . '_info') . '_' . $iNow;
+                $sSearchType = isset($aField['search_type']) ? $aField['search_type'] : reset(BxDolSearchExtended::$TYPE_TO_TYPE_SEARCH[$aField['type']]);
+                $sSearchValue =  isset($aField['search_value']) ? $aField['search_value'] : (in_array($aField['type'], array('checkbox', 'switcher')) ? $aField['value'] : '');
+                if ($sSearchType == 'datepicker_range_age' || $sSearchType == 'datepicker_range'){
+                    $sSearchValue =  BxDolService::getSerializedService($aObject['module'], 'get_search_options', array($sField, $aField['type'], $sSearchType));
+                }
+                $iActive = isset($aField['active']) ? (int)$aField['active'] : 1;
+
+                $bResult = (int)$oDb->query("INSERT INTO `sys_search_extended_fields`(`object`, `name`, `type`, `caption`, `info`, `values`, `pass`, `search_type`, `search_value`, `search_operator`, `active`, `order`) VALUES(:object, :name, :type, :caption, :info, :values, :pass, :search_type, :search_value, :search_operator, :active, :order)", array(
+                    'object' => $aObject['object'], 
+                    'name' => $sField,
+                    'type' => $aField['type'],
+                    'caption' => $sCaptionKey,
+                    'info' => $sInfoKey,
+                    'values' => $aField['values'],
+                    'pass' => $aField['pass'],
+                    'search_type' => $sSearchType,
+                    'search_value' => $sSearchValue, 
+                    'search_operator' => isset($aField['search_operator']) ? $aField['search_operator'] : reset(BxDolSearchExtended::$TYPE_TO_OPERATOR[$aField['type']]),
+                    'active' => $iActive,
+                    'order' => $iOrder++
+                )) > 0;
+
+                if($bResult) {
+                    $aCaptionValues = !empty($aField['caption']) ? $oLanguage->getLanguageString($aField['caption']) : array();
+                    $aCaptionSystemValues = !empty($aField['caption_system']) ? $oLanguage->getLanguageString($aField['caption_system']) : array();
+                    $aInfoValues = !empty($aField['info']) ? $oLanguage->getLanguageString($aField['info']) : array();
+
+                    foreach($aLangIds as $iLangId) {
+                        $sCaptionValue = '';
+                        if(isset($aCaptionValues[$iLangId]) && !empty($aCaptionValues[$iLangId]['string']))
+                            $sCaptionValue = $aCaptionValues[$iLangId]['string'];
+                        if(empty($sCaptionValue) && isset($aCaptionSystemValues[$iLangId]) && !empty($aCaptionSystemValues[$iLangId]['string']))
+                            $sCaptionValue = $aCaptionSystemValues[$iLangId]['string'];
+                        $oLanguage->addLanguageString($sCaptionKey, $sCaptionValue, $iLangId, 0, false);
+
+                        $sInfoValue = '';
+                        if(isset($aInfoValues[$iLangId]) && !empty($aInfoValues[$iLangId]['string']))
+                            $sInfoValue = $aInfoValues[$iLangId]['string'];
+                        $oLanguage->addLanguageString($sInfoKey, $sInfoValue, $iLangId, 0, false);
+                    }
+                }
+            }
+
+            $oLanguage->compileLanguage(0, true);
+
+            $aFields = $oDb->getAll($sQueryFields, $aQueryFieldsBindings);
+        }
+
+        //--- Process fields
+        foreach ($aFields as $iIndex => $aField) {
+            //--- Process field Values            
+            if(!empty($aField['values'])) {
+                if(strncmp(BX_DATA_LISTS_KEY_PREFIX, $aField['values'], 2) === 0) {
+                    $sList = trim($aField['values'], BX_DATA_LISTS_KEY_PREFIX . ' ');
+                    $aFields[$iIndex] = array_merge($aField, array(
+                    	'values' => BxDolFormQuery::getDataItems($sList),
+                    	'values_list_name' => $sList,
+                    ));
+                }
+                else if(BxDolService::isSerializedService($aField['values']))
+                    $aFields[$iIndex]['values'] = BxDolService::callSerialized($aField['values']);
+                else
+                    $aFields[$iIndex]['values'] = unserialize($aField['values']);
+            }
+        }
+
+        return $aFields;
+    }
+
+    public function deleteFields($aWhere)
+    {
+        if(empty($aWhere))
+    		return false;
+
+        $sWhereClause = $this->arrayToSQL($aWhere, ' AND ');
+
+        $aFields = $this->getAll("SELECT * FROM `sys_search_extended_fields` WHERE " . $sWhereClause);
+        if(!empty($aFields) && is_array($aFields)) {
+            $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+
+            foreach($aFields as $aField) {
+                if(!empty($aField['caption']))
+                    $oLanguage->deleteLanguageString($aField['caption'], 0, false);
+
+                if(!empty($aField['info']))
+                    $oLanguage->deleteLanguageString($aField['info'], 0, false);
+            }
+
+            $oLanguage->compileLanguage(0, true);
+        }
+
+        return $this->query("DELETE FROM `sys_search_extended_fields` WHERE " . $sWhereClause);
+    }
+}
+
+/** @} */

+ 2840 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolTemplate.php

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

+ 78 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolViewQuery.php

@@ -0,0 +1,78 @@
+<?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 BxDolView
+ */
+class BxDolViewQuery extends BxDolObjectQuery
+{
+    protected $_iPeriod;
+
+    public function __construct(&$oModule)
+    {
+        parent::__construct($oModule);
+
+        $this->_sTableTrackFieldAuthor = 'viewer_id';
+
+        $aSystem = $this->_oModule->getSystemInfo();
+        $this->_iPeriod = (int)$aSystem['period'];
+    }
+
+    public function isPerformed($iObjectId, $iAuthorId)
+    {
+        $sQuery = $this->prepare("SELECT `date` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `viewer_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+        return (int)$this->getOne($sQuery) > 0;
+    }
+
+	public function getPerformedBy($iObjectId)
+    {
+        $sQuery = $this->prepare("SELECT `viewer_id` AS `id`, COUNT(`viewer_id`) AS `count` FROM `{$this->_sTableTrack}` WHERE `object_id`=? GROUP BY `viewer_id` ORDER BY `date` DESC", $iObjectId);
+        return $this->getAll($sQuery);
+    }
+
+	public function getView($iObjectId)
+    {
+        return array('count' => $this->getObjectCount($iObjectId));
+    }
+
+    public function doView($iObjectId, $iAuthorId, $sAuthorIp)
+    {
+        $iAuthorNip = ip2long($sAuthorIp);
+
+        $aBindings = array(
+        	'object_id' => $iObjectId
+        );
+        if($iAuthorId) {
+        	$aBindings['viewer_id'] = $iAuthorId;
+
+            $sWhere = " AND `viewer_id` = :viewer_id ";
+        }
+        else {
+        	$aBindings['viewer_nip'] = $iAuthorNip;
+
+            $sWhere = " AND `viewer_id` = '0' AND `viewer_nip` = :viewer_nip ";
+        }
+
+        $iDate = (int)$this->getOne("SELECT `date` FROM `{$this->_sTableTrack}` WHERE `object_id` = :object_id " . $sWhere, $aBindings);
+        $iDateNow = time();
+
+        if(!$iDate || ($iDateNow - $iDate) > $this->_iPeriod) {
+            $sQuery = $this->prepare("INSERT IGNORE INTO `{$this->_sTableTrack}` SET `object_id` = ?, `viewer_id` = ?, `viewer_nip` = ?, `date` = ?", $iObjectId, $iAuthorId, $iAuthorNip, $iDateNow);
+            return (int)$this->query($sQuery) > 0;
+        }
+    }
+
+    public function updateTriggerTable($iObjectId)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldCount}` = `{$this->_sTriggerFieldCount}` + 1 WHERE `{$this->_sTriggerFieldId}` = ?", $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+}
+
+/** @} */

+ 189 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/classes/BxDolVoteReactions.php

@@ -0,0 +1,189 @@
+<?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
+ * @{
+ */
+
+/**
+ * Like based votes for any content
+ */
+class BxDolVoteReactions extends BxTemplVote
+{
+    protected $_sDataList; //--- Reactions list name.
+    protected $_aDataList; //--- Reactions list with all reactions.
+
+    protected $_sDefault; //--- Default reaction name.
+
+    public function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_oQuery = new BxDolVoteReactionsQuery($this);
+        $this->_sType = BX_DOL_VOTE_TYPE_REACTIONS;
+
+        $this->_sDataList = 'sys_vote_reactions';
+        $this->_aDataList = array();
+
+        $this->_sDefault = '';
+    }
+
+    public function init($iId)
+    {
+        if(!parent::init($iId))
+            return false;
+
+        $aReactions = BxDolFormQuery::getDataItems($this->_sDataList, false, BX_DATA_VALUES_ALL);
+
+        foreach($aReactions as $sReaction => $aReaction) {
+            $aData = !empty($aReaction['Data']) ? unserialize($aReaction['Data']) : array();
+
+            $this->_aDataList[$sReaction] = array(
+                'name' => $sReaction,
+                'title' => $aReaction['LKey'],
+                'title_aux' => $aReaction['LKey2'],
+                'icon' => isset($aData['icon']) ? $aData['icon'] : '',
+                'weight' => isset($aData['weight']) ? $aData['weight'] : 1,
+            );
+        }
+
+        if(!empty($this->_aDataList)) {
+            $aNames = array_keys($this->_aDataList);
+            $this->_sDefault = array_shift($aNames);
+        }
+    }
+    /**
+     * Interface functions for outer usage
+     */
+    public function getValue()
+    {
+        return (int)$this->_aSystem['min_value'];
+    }
+
+    public function getReaction($sName)
+    {
+        if(empty($this->_aDataList))
+            $this->getReactions();
+
+        return !empty($this->_aDataList[$sName]) ? $this->_aDataList[$sName] : false;
+    }
+
+    public function getReactions($bFullInfo = false)
+    {
+        return $bFullInfo ? $this->_aDataList : array_keys($this->_aDataList);
+    }
+
+    public function getTrackBy($aParams)
+    {
+        return $this->_oQuery->getTrackBy($aParams);
+    }
+
+    /**
+     * Actions functions
+     */
+    public function actionGetDoVotePopup()
+    {
+        if(!$this->isEnabled())
+           return '';
+
+        return $this->_getDoVotePopup((int)bx_get('value'));
+    }
+
+    public function actionGetVotedBy()
+    {
+        if (!$this->isEnabled())
+           return '';
+
+        $sReaction = bx_get('reaction');
+        $sReaction = $sReaction !== false ? bx_process_input($sReaction) : $this->_sDefault;
+
+        return $this->_getVotedBy(array('reaction' => $sReaction));
+    }
+
+    /**
+     * Internal functions
+     */
+    protected function _isDuplicate($iObjectId, $iAuthorId, $iAuthorIp, $bVoted)
+    {
+        return $bVoted && !$this->isUndo();
+    }
+
+    protected function _isCount($aVote = array())
+    {
+        if(empty($aVote))
+            $aVote = $this->_getVote();
+
+        foreach($aVote as $sKey => $mixedValue)
+            if(substr($sKey, 0, 5) == 'count' && (int)$mixedValue != 0)
+                return true;
+
+        return false;
+    }
+
+    protected function _getVoteData()
+    {
+        $aData = parent::_getVoteData();
+        if($aData === false)
+            return false;
+
+        $sReaction = bx_get('reaction');
+        if($sReaction === false)
+            return false;
+
+        $aData['reaction'] = bx_process_input($sReaction);
+        return $aData;
+    }
+
+    protected function _returnVoteData($iObjectId, $iAuthorId, $iAuthorIp, $aData, $bVoted)
+    {
+        $sReaction = $aData['reaction'];
+
+        $bUndo = $this->isUndo();
+        $bDisabled = $bVoted && !$bUndo;
+
+        $aVote = $this->_getVote($iObjectId, true);
+        $aTrack = $bVoted ? $this->_getTrack($iObjectId, $iAuthorId) : array();
+
+        $sJsClick = '';
+        if(!$bDisabled)
+            $sJsClick = $bVoted && $bUndo ? $this->getJsClickDo($sReaction) : $this->getJsClick();
+
+        $iCount = (int)$aVote['count_' . $sReaction];
+        return array(
+            'code' => 0,
+            'reaction' => $sReaction,
+            'rate' => $aVote['rate_' . $sReaction],
+            'count' => $iCount,
+            'countf' => $iCount > 0 ? $this->_getLabelCounter($iCount, array('reaction' => $sReaction)) : '',
+            'label_icon' => $this->_getIconDoWithTrack($bVoted, $aTrack),
+            'label_title' => _t($this->_getTitleDoWithTrack($bVoted, $aTrack)),
+            'label_click' => $sJsClick,
+            'disabled' => $bVoted && !$this->isUndo(),
+        );
+    }
+
+    protected function _getIconDoWithTrack($bVoted, $aTrack = array())
+    {
+        $sReaction = $bVoted ? $aTrack['reaction'] : $this->_sDefault;
+
+    	return $this->_aDataList[$sReaction]['icon'];
+    }
+
+    protected function _getTitleDoWithTrack($bVoted, $aTrack = array())
+    {
+        $sReaction = $bVoted ? $aTrack['reaction'] : $this->_sDefault;
+
+    	return $this->_aDataList[$sReaction]['title'];
+    }
+    
+    protected function _getTitleDoBy($aParams = array())
+    {
+        $sReaction = !empty($aParams['reaction']) ? $aParams['reaction'] : $this->_sDefault;
+
+    	return _t('_vote_do_by_reactions', _t($this->_aDataList[$sReaction]['title']));
+    }
+}
+
+/** @} */

+ 1741 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/inc/utils.inc.php

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

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

+ 86 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/files/classes/BxBaseModFilesFormsEntryHelper.php

@@ -0,0 +1,86 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseFile Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Entry forms helper functions
+ */
+class BxBaseModFilesFormsEntryHelper extends BxBaseModTextFormsEntryHelper
+{
+    protected $_sDisplayForFormAdd;
+    protected $_sObjectNameForFormAdd;
+    
+    public function __construct($oModule)
+    {
+        parent::__construct($oModule);
+    }
+    
+    public function getObjectFormAdd ($sDisplay = false)
+    {
+        if (false === $sDisplay)
+            $sDisplay = $this->_sDisplayForFormAdd;
+
+        $oForm = BxDolForm::getObjectInstance($this->_sObjectNameForFormAdd, $sDisplay, $this->_oModule->_oTemplate);
+        if($this->_bAjaxMode)
+            $oForm->setAjaxMode($this->_bAjaxMode);
+
+        if($this->_bAbsoluteActionUrl)
+            $this->_setAbsoluteActionUrl('add', $oForm);
+
+        return $oForm;
+    }
+    
+    protected function addDataFormAction ($sDisplay = false, $sCheckFunction = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bAsJson = false;
+
+        // get form object
+        $oForm = $this->getObjectFormAdd();
+        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->checkAllowedAdd()))
+            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 ();
+        $aContentIds = $oForm->insert ($aValsToAdd);
+        if (false === $aContentIds || !is_array($aContentIds)) {
+            if (!$oForm->isValid() || !is_array($aContentIds))
+                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');
+        }
+
+        foreach ($aContentIds as $iContentId) {
+            $sResult = $this->onDataAddAfter (getLoggedId(), $iContentId);
+            if ($sResult)
+                return $sResult;
+        }
+        
+        return array('need_redirect_after_action' => true, 'content_ids_array' => $aContentIds);
+    }
+}
+
+/** @} */

+ 258 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralDb.php

@@ -0,0 +1,258 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Module database queries
+ */
+class BxBaseModGeneralDb extends BxDolModuleDb
+{
+    protected $_oConfig;
+
+    public function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+        $this->_oConfig = $oConfig;
+    }
+
+    public function getContentInfoById ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['TABLE_ENTRIES']) || empty($CNF['FIELD_ID']))
+            return array();
+
+        $sQuery = $this->prepare ("SELECT * FROM `" . $CNF['TABLE_ENTRIES'] . "` WHERE `" . $CNF['FIELD_ID'] . "` = ?", $iContentId);
+        return $this->getRow($sQuery);
+    }
+
+    function getEntriesBy($aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query', 1 => array()));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        $sSelectClause = "`" . $CNF['TABLE_ENTRIES'] . "`.*";
+
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1]['id'] = (int)$aParams['id'];
+
+                $sWhereClause .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ID'] . "` = :id";
+                break;
+
+            case 'author':
+            	$aMethod['name'] = 'getRow';
+                $aMethod['params'][1]['author'] = (int)$aParams['author'];
+
+                $sWhereClause .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_AUTHOR'] . "` = :author";
+                break;
+
+            case 'search_ids':
+                $this->_getEntriesBySearchIds($aParams, $aMethod, $sSelectClause, $sJoinClause, $sWhereClause, $sOrderClause, $sLimitClause);
+                break;
+
+            case 'all_ids':
+                $aMethod['name'] = 'getColumn';
+
+                $sSelectClause = "`" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ID'] . "`";
+                $sOrderClause .=  "`" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ADDED'] . "` ASC";
+                break;
+
+            case 'all':
+                $sOrderClause .=  "`" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ADDED'] . "` ASC";
+                break;
+        }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = 'ORDER BY ' . $sOrderClause;
+
+        if(!empty($sLimitClause))
+            $sLimitClause = 'LIMIT ' . $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_ENTRIES'] . "` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+		return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getEntriesByAuthor ($iProfileId)
+    {
+        $sQuery = $this->prepare ("SELECT * FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `" . $this->_oConfig->CNF['FIELD_AUTHOR'] . "` = ? ORDER BY `" . $this->_oConfig->CNF['FIELD_ADDED'] . "` DESC", $iProfileId);
+        return $this->getAll($sQuery);
+    }
+
+    public function getEntriesNumByAuthor ($iProfileId)
+    {
+        $sQuery = $this->prepare ("SELECT COUNT(*) FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `" . $this->_oConfig->CNF['FIELD_AUTHOR'] . "` = ?", $iProfileId);
+        return $this->getOne($sQuery);
+    }
+    
+    public function updateEntriesBy($aParamsSet, $aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['TABLE_ENTRIES']) || empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        return $this->query("UPDATE `" . $CNF['TABLE_ENTRIES'] . "` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function publishEntries()
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        $aEntries = $this->getAll("SELECT `id`, `" . $CNF['FIELD_PUBLISHED'] . "`, FROM_UNIXTIME(`" . $CNF['FIELD_PUBLISHED'] . "`)  FROM `" . $CNF['TABLE_ENTRIES'] . "` WHERE `" . $CNF['FIELD_PUBLISHED'] . "` > `" . $CNF['FIELD_ADDED'] . "` AND `" . $CNF['FIELD_STATUS'] . "` = 'awaiting'");
+        if(empty($aEntries) || !is_array($aEntries))
+            return false;
+
+        $iNow = time();
+        $aResult = array();
+        foreach($aEntries as $aEntry)
+            if($aEntry[$CNF['FIELD_PUBLISHED']] <= $iNow) 
+                $aResult[] = $aEntry[$CNF['FIELD_ID']];
+
+        $sSet = "`" . $CNF['FIELD_ADDED'] . "`=`" . $CNF['FIELD_PUBLISHED'] . "`, `" . $CNF['FIELD_STATUS'] . "` = 'active'";
+        if(isset($CNF['FIELD_CHANGED']))
+            $sSet .= ", `" . $CNF['FIELD_CHANGED'] . "`=`" . $CNF['FIELD_PUBLISHED'] . "`";
+
+        return count($aResult) == (int)$this->query("UPDATE `" . $CNF['TABLE_ENTRIES'] . "` SET " . $sSet . " WHERE `id` IN (" . $this->implode_escape($aResult) . ")") ? $aResult : false;
+    }
+
+    public function alterFulltextIndex ()
+    {
+        $CNF = $this->_oConfig->CNF;        
+
+        $bFulltextIndex = false;
+        $aIndexes = $this->getAll("SHOW INDEXES FROM `" . $CNF['TABLE_ENTRIES'] . "`");
+
+        foreach ($aIndexes as $r) {
+            if ($CNF['TABLE_ENTRIES_FULLTEXT'] == $r['Key_name']) {
+                $bFulltextIndex = true;
+                break;
+            }
+        }
+
+        if ($bFulltextIndex)
+            $this->query("ALTER TABLE `" . $CNF['TABLE_ENTRIES'] . "` DROP INDEX `" . $CNF['TABLE_ENTRIES_FULLTEXT'] . "`");
+
+        $sFields = getParam($CNF['PARAM_SEARCHABLE_FIELDS']);
+        if (!$sFields || !($aFields = explode(',', $sFields)))
+            return true;
+
+        $sFields = '';
+        foreach ($aFields as $s)
+            $sFields .= "`$s`,";
+
+        return $this->query("ALTER TABLE `" . $CNF['TABLE_ENTRIES'] . "` ADD FULLTEXT `" . $CNF['TABLE_ENTRIES_FULLTEXT'] . "` (" . trim($sFields, ', ') . ")");
+    }
+
+    protected function _getEntriesBySearchIds($aParams, &$aMethod, &$sSelectClause, &$sJoinClause, &$sWhereClause, &$sOrderClause, &$sLimitClause)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod['name'] = 'getColumn';
+
+        $sSelectClause = " DISTINCT `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_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("%" . preg_replace('/\s+/', '%', $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;
+
+                case 'locate':
+                    if(!isset($CNF['OBJECT_METATAGS']))
+                        break;
+                    if ($aSearchParam['type'] == 'location_radius'){
+                        list($fLatitude, $fLongitude, $sCountry, $sState, $sCity, $sZip, $sStreet, $sStreetNumber, $iRadius) = $aSearchParam['value']['array'];
+                        $aBounds = bx_get_location_bounds_latlng($fLatitude, $fLongitude, $iRadius);   
+                        $aSql = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS'])->locationsGetAsSQLPart($CNF['TABLE_ENTRIES'], $CNF['FIELD_ID'], '', '', '', '', $aBounds);
+                    }
+                    else{
+                        list($fLatitude, $fLongitude, $sCountry, $sState, $sCity, $sZip) = $aSearchParam['value']['array'];
+                        $aSql = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS'])->locationsGetAsSQLPart($CNF['TABLE_ENTRIES'], $CNF['FIELD_ID'], $sCountry, $sState, $sCity, $sZip);
+                    }
+                    if(!empty($aSql['where'])) {
+                        $sWhereConditions .= $aSql['where'];
+
+                        if(!empty($aSql['join']))
+                            $sJoinClause .= $aSql['join'];
+                    }
+                    break;
+                    
+                case 'between':
+                    if(!is_array($aSearchParam['value']) || count($aSearchParam['value']) != 2) 
+                        break;
+
+                    $sWhereConditions .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $sSearchParam . "` >= :" . $sSearchParam . "_from";
+                    $sWhereConditions .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $sSearchParam . "` <= :" . $sSearchParam . "_to";
+
+                    $aMethod['params'][1][$sSearchParam . "_from"] = $aSearchParam['value'][0]; 
+                    $aMethod['params'][1][$sSearchParam . "_to"] = $aSearchParam['value'][1]; 
+                    break;
+
+                default:
+                    if(empty($aSearchParam['operator']))
+                        break;
+
+                    $sSearchValue = " " . $aSearchParam['operator'] . " :" . $sSearchParam;
+                    $aMethod['params'][1][$sSearchParam] = $aSearchParam['value'];
+            }
+
+            if(!empty($sSearchValue))
+                $sWhereConditions .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $sSearchParam . "`" . $sSearchValue;
+        }
+
+        $sWhereClause .= " AND (" . $sWhereConditions . ")"; 
+
+        $this->_getEntriesBySearchIdsOrder($aParams, $sOrderClause);
+    }
+
+    protected function _getEntriesBySearchIdsOrder($aParams, &$sOrderClause)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParams['search_params']['order']) || !is_array($aParams['search_params']['order'])) 
+            $aParams['search_params']['order'] = array(
+                array('table' => $CNF['TABLE_ENTRIES'], 'field' => $CNF['FIELD_ADDED'], 'direction' => 'DESC')
+            );
+
+        $aOrders = array();
+        foreach($aParams['search_params']['order'] as $aOrder) 
+            $aOrders[] = "`" . (isset($aOrder['table']) ? $aOrder['table'] : $CNF['TABLE_ENTRIES']) . "`.`" . (!empty($aOrder['field']) ? $aOrder['field'] : $CNF['FIELD_ADDED']) . "` " . (!empty($aOrder['direction']) ? strtoupper($aOrder['direction']) : 'DESC');
+
+        $sOrderClause .= implode(', ', $aOrders);
+    }
+}
+
+/** @} */

+ 578 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php

@@ -0,0 +1,578 @@
+<?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);
+        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');
+
+        // perform action
+        $this->_oModule->$sCheckFunction(true);
+
+        // redirect
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        $this->redirectAfterAdd($aContentInfo);
+    }
+    
+    public function redirectAfterAdd($aContentInfo)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$sUrl = 'page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']];
+        if($this->_bAjaxMode) {
+            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;
+
+        // perform action
+        $this->_oModule->$sCheckFunction($aContentInfo, true);
+        
+        // redirect
+        $this->redirectAfterEdit($aContentInfo);
+    }
+
+    protected function redirectAfterEdit($aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $this->_redirectAndExit('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function deleteDataForm ($iContentId, $sDisplay = false, $sCheckFunction = false)
+    {
+        if (!$sCheckFunction)
+            $sCheckFunction = 'checkAllowedDelete';
+        
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // get content data and profile info
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        if (!$aContentInfo)
+            return MsgBox(_t('_sys_txt_error_entry_is_not_defined'));
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->$sCheckFunction($aContentInfo)))
+            return MsgBox($sMsg);
+
+        // check and display form
+        $oForm = $this->getObjectFormDelete($sDisplay);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $oForm->initChecker($aContentInfo);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        if ($sError = $this->deleteData($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $oProfile, $oForm))
+            return MsgBox($sError);
+
+        // perform action
+        $this->_oModule->$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)
+    {
+        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;
+
+        list($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+
+        if(isset($CNF['FIELD_PHOTO']))
+            $oForm->processFiles ($CNF['FIELD_PHOTO'], $iContentId, false);
+
+        if(!empty($CNF['OBJECT_METATAGS'])) { // && isset($aTrackTextFieldsChanges['changed_fields'][$CNF['FIELD_TEXT']])) { // TODO: check if aTrackTextFieldsChanges works 
+            $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);
+            }
+        }
+
+        if(isset($CNF['FIELD_STATUS']) && ($aTrackResult = $oForm->isTrackFieldChanged($CNF['FIELD_STATUS'], true)) !== false)
+            if($aTrackResult['old'] == 'failed' && $aTrackResult['new'] == 'active')
+                $this->_alertAfterAdd($aContentInfo);
+
+        return '';
+    }
+
+    public function onDataAddAfter ($iAccountId, $iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(($oForm = $this->getObjectFormAdd()) !== false) {
+            if(isset($CNF['FIELD_PHOTO']))
+                $oForm->processFiles($CNF['FIELD_PHOTO'], $iContentId, true);
+        }
+
+        $this->_processMetas($iAccountId, $iContentId);
+
+        return '';
+    }
+
+    protected function _processMetas($iAccountId, $iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($aContentInfo)
+                $oMetatags->metaAddAuto($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $CNF, $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']);
+            if ($oMetatags->locationsIsEnabled() && $aContentInfo)
+                $oMetatags->locationsAddFromForm($aContentInfo[$CNF['FIELD_ID']], $CNF['FIELD_LOCATION_PREFIX']);
+
+            if ($aContentInfo && !empty($CNF['FIELD_LABELS']) && ($aLabels = bx_get($CNF['FIELD_LABELS'])) && $oMetatags->keywordsIsEnabled()) {
+                foreach ($aLabels as $sLabel)
+                    $oMetatags->keywordsAddOne($aContentInfo[$CNF['FIELD_ID']], $sLabel, false);
+            }
+        }
+    }
+
+    protected function prepareCustomRedirectUrl($s, $aContentInfo)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $oProfile = BxDolProfile::getInstanceByContentAndType($aContentInfo[$CNF['FIELD_ID']], $this->_oModule->getName());
+
+        $aMarkers = array(
+            '{profile_id}',
+            '{content_id}',
+            '{module}',
+        );
+        $aReplacements = array(
+            $oProfile ? $oProfile->id() : 0,
+            $aContentInfo[$CNF['FIELD_ID']],
+            $this->_oModule->getName(),
+        );
+        $s = str_replace($aMarkers, $aReplacements, $s);
+
+        $s = BxDolPermalinks::getInstance()->permalink($s);
+
+        if (false === strpos($s, 'http://') && false === strpos($s, 'https://'))
+            $s = BX_DOL_URL_ROOT . $s;
+        
+        return $s;
+    }
+
+    protected function prepareResponse($mixedResponse, $bAsJson = false, $sKey = 'msg', $aAdditional = array())
+    {
+    	if(!$bAsJson)
+            return $mixedResponse;
+
+        $aResponse = array(
+            $sKey => $mixedResponse,
+            '_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]));
+    }
+}
+
+/** @} */

+ 1763 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralModule.php

@@ -0,0 +1,1763 @@
+<?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);
+
+    	$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;
+
+        $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($aParams['context_id']) && !empty($CNF[$sKey]) && !empty($oForm->aInputs[$CNF[$sKey]])) {
+            foreach($oForm->aInputs[$CNF[$sKey]]['values'] as $aValue)
+                if(isset($aValue['key']) && (int)$aValue['key'] == -(int)$aParams['context_id']) {
+                    $oForm->aInputs[$CNF[$sKey]]['value'] = -(int)$aParams['context_id'];
+                    $oForm->aInputs[$CNF[$sKey]]['type'] = 'hidden';
+                    break;
+                }
+        }
+
+        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 && $iAuthorIdAbs == (int)$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']);
+
+        return array(
+            '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' => $this->_getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams), //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.
+        );
+    }
+
+    /**
+     * 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) {
+            if ($this instanceof iBxDolProfileService) {
+                $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+            }
+            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(is_numeric($aDataEntry))
+            $aDataEntry = $this->_oDb->getContentInfoById((int)$aDataEntry);
+
+        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 onPublished($iContentId) {}
+
+    public function onFailed($iContentId) {}
+
+    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;
+    }
+}
+
+/** @} */

+ 126 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/classes/BxBaseModGeneralPageEntry.php

@@ -0,0 +1,126 @@
+<?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');
+
+/**
+ * Entry create/edit pages
+ */
+class BxBaseModGeneralPageEntry extends BxTemplPage
+{
+    protected $MODULE;
+
+    protected $_oModule;
+    protected $_aContentInfo = false;
+
+    protected $_sCoverClass;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        parent::__construct($aObject, $oTemplate ? $oTemplate : $this->_oModule->_oTemplate);
+
+        $this->_sCoverClass = $this->_oModule->getName() . '_cover';
+    }
+
+    public function isActive()
+    {
+        return true;
+    }
+
+    public function getCode ()
+    {
+        // check if content exists
+        if (!$this->_aContentInfo || !$this->isActive()) { // if entry is not found - display standard "404 page not found" page
+            $this->_oTemplate->displayPageNotFound();
+            exit;
+        }
+
+        $oCover = BxDolCover::getInstance($this->_oModule->_oTemplate);        
+        if ($oCover)
+            $oCover->setCoverClass($this->_sCoverClass);
+
+        // permissions check 
+        $this->_processPermissionsCheck ();
+
+        // count views
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if(!empty($CNF['OBJECT_VIEWS'])) {
+            $oView = BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $this->_aContentInfo[$CNF['FIELD_ID']]);
+            if($oView && $oView->isEnabled())
+                $oView->doView();
+        }
+
+        // set cover image
+        $mixedCover = method_exists($this, '_getImageForPageCover') ? $this->_getImageForPageCover() : $this->_getThumbForMetaObject();
+        if($mixedCover !== false) {
+            $aCover = array(
+                'id' => $mixedCover['id']
+            );
+
+            if(!empty($mixedCover['transcoder']))
+                $aCover['transcoder'] = $mixedCover['transcoder'];
+            else if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_COVER'])) 
+                $aCover['transcoder'] = $CNF['OBJECT_IMAGES_TRANSCODER_COVER'];
+            else if(!empty($mixedCover['object']))
+                $aCover['object'] = $mixedCover['object'];
+
+            $oCover->setCoverImageUrl($aCover);
+        }
+
+        // add content metatags
+        $mixedThumb = $this->_getThumbForMetaObject();
+        if (!empty($CNF['OBJECT_METATAGS']) && $mixedThumb) {
+            $o = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            if ($o)
+                $o->addPageMetaInfo($this->_aContentInfo[$CNF['FIELD_ID']], $mixedThumb);
+        }
+
+        $oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        if($oMenuSubmenu) {
+            // add actions menu to submenu
+            if(isset($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']))
+                $oMenuSubmenu->setObjectActionsMenu($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+
+            // add social sharing menu to submenu
+            $oMenuSubmenu->setServiceSocialSharing(array(
+                'module' => $this->MODULE,
+                'method' => 'entity_social_sharing',
+            ));
+        }
+
+        return parent::getCode ();
+    }
+
+    protected function _processPermissionsCheck ()
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedView($this->_aContentInfo))) {
+            $this->_oTemplate->displayAccessDenied($sMsg);
+            exit;
+        }
+        $this->_oModule->checkAllowedView($this->_aContentInfo, true);
+    }
+
+    protected function _getThumbForMetaObject ()
+    {
+        return $this->_oModule->getEntryImageData($this->_aContentInfo);
+    }
+
+    protected function _getPageCacheParams ()
+    {
+        if (!$this->_aContentInfo)
+            return '';
+        return $this->_aContentInfo[$this->_oModule->_oConfig->CNF['FIELD_ID']]; // cache is different for every entry
+    }
+}
+
+/** @} */

+ 250 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/general/template/css/main.css

@@ -0,0 +1,250 @@
+
+/* breadcrumb */
+div.bx-base-general-breadcrumb  {
+    white-space: nowrap;
+    overflow: hidden;
+}
+div.bx-base-general-breadcrumb a,
+div.bx-base-general-breadcrumb i {
+    display: inline-block;
+    vertical-align: top;
+
+    height: 2.2rem;
+    line-height: 2.2rem;
+}
+.bx-base-general-breadcrumb-home i.sys-icon {
+    font-size: 2.0rem;
+}
+
+/* view entry */
+.bx-base-general-entity-info > p:first-child {
+    margin-top:0;
+}
+
+
+/* view entry: actions */
+.bx-base-general-entity-actions ul.bx-menu-hor {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+}
+.bx-base-general-entity-actions ul.bx-menu-hor.bx-menu-more-auto {
+    -webkit-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+}
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item {
+    white-space: nowrap;
+}
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .cmt-element-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .cmt-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-view-element-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-view-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-vote-element-holder, 
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-vote-counter-holder, 
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-score-element-holder, 
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-score-counter-holder, 
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-favorite-element-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-favorite-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-report-element-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-report-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-element-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-counter-holder {
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+}
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .cmt-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-view-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-vote-counter-holder, 
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-score-counter-holder, 
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-favorite-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-report-counter-holder,
+.bx-base-general-entity-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-counter-holder {
+    line-height: normal;
+}
+.bx-base-general-entity-actions .cmt,
+.bx-base-general-entity-actions .bx-view,
+.bx-base-general-entity-actions .bx-vote,
+.bx-base-general-entity-actions .bx-score,
+.bx-base-general-entity-actions .bx-favorite,
+.bx-base-general-entity-actions .bx-feature,
+.bx-base-general-entity-actions .bx-report,
+.bx-base-general-entity-actions .bx-tl-repost,
+.bx-base-general-entity-actions .bx-menu-btns-hor.bx-menu-object-sys_social_sharing,
+.bx-base-general-entity-actions .bx-base-general-entity-action {
+    position: relative;
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+
+    margin: 0;
+    padding: 0;
+}
+
+/* TODO: Remove this at the end */
+.bx-base-general-entity-share .bx-base-general-comments,
+.bx-base-general-entity-share .bx-view,
+.bx-base-general-entity-share .bx-vote,
+.bx-base-general-entity-share .bx-score,
+.bx-base-general-entity-share .bx-favorite,
+.bx-base-general-entity-share .bx-feature,
+.bx-base-general-entity-share .bx-report,
+.bx-base-general-entity-share .bx-tl-repost,
+.bx-base-general-entity-share .bx-menu-btns-hor.bx-menu-object-sys_social_sharing {
+	position: relative;
+	float: none;
+	display: inline-block;
+	vertical-align: top;
+}
+
+/* attachments */
+.bx-base-general-attachments {
+    margin:-1px;
+}
+.bx-base-general-attachment {
+    margin:1px;
+    text-align: center;
+}
+.bx-base-general-attachments .bx-base-general-attachment
+.bx-base-general-attachments .bx-base-general-attachment img,
+.bx-base-general-attachments .bx-base-general-attachment video {
+    width:18.75rem;
+    height:12.5rem;
+}
+.bx-base-general-attachment a div {
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+/* comments item */
+div.bx-base-general-comments div.bx-base-general-comments-element-holder,
+div.bx-base-general-comments div.bx-base-general-comments-counter-holder {
+    position: relative;
+    float: left;
+}
+.bx-base-general-comments-element-holder
+.bx-base-general-comments-counter-holder {
+    line-height: 20px;
+    line-height: 1.25rem;
+}
+.bx-base-general-comments-button .bx-base-general-comments-element-holder,
+.bx-base-general-comments-button .bx-base-general-comments-counter-holder {
+    line-height: 36px;
+    line-height: 2.2rem;
+}
+.bx-base-general-comments-button .bx-base-general-comments-counter-holder {
+    font-size: 1.5rem;
+}
+.bx-base-general-comments-link .bx-base-general-comments-counter-holder.bx-btn-border {
+    border-width: 0px;
+}
+
+/* unit */
+.bx-def-unit-live-search {
+    width:236px;
+    width:14.75;
+    overflow:hidden;
+}
+
+.bx-def-unit-live-search .bx-def-unit-info {
+    width:188px;
+    width:11.75;
+}
+
+.bx-def-unit-live-search .bx-def-unit-img:not(.empty) {
+    background-size:cover;
+    background-repeat:no-repeat;
+    background-position:center center;
+}
+.bx-def-unit-live-search .bx-def-unit-img-empty {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+.bx-def-unit-live-search .bx-def-unit-img-empty i {
+    font-size: 1.2rem;
+}
+
+/* unit meta */
+.bx-base-general-unit-meta {
+    white-space: normal;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+.sys-author ul.bx-menu-custom.bx-menu-hor,
+.bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+
+    margin: 0.1rem 0 0.2rem 0;
+}
+.sys-author ul.bx-menu-custom.bx-menu-hor > li,
+.bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor > li {
+    -webkit-flex: 0 1 auto; 
+    -ms-flex:  0 1 auto; 
+    flex: 0 1 auto;
+
+    white-space: nowrap;
+}
+.sys-author ul.bx-menu-custom.bx-menu-hor > li.bx-menu-item-nl,
+.bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor > li.bx-menu-item-nl {
+    display: none;
+    width: 100%;
+    height: 0.5rem;
+}
+.bx-base-pofile-unit-with-cover .bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor > li.bx-menu-item-nl {
+    display: block;
+}
+
+.sys-author ul.bx-menu-custom.bx-menu-hor > li > span,
+.bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor > li > span {
+    margin-left: 0;
+    margin-right: 0;
+}
+.sys-author ul.bx-menu-custom li:first-child .bx-base-general-unit-meta-div,
+.bx-base-general-unit-meta ul.bx-menu-custom li:first-child .bx-base-general-unit-meta-div {
+    display: none;
+}
+
+.sys-author ul.bx-menu-custom li .bx-base-general-unit-meta-item,
+.sys-author ul.bx-menu-custom li .bx-base-general-unit-meta-div,
+.sys-author ul.bx-menu-custom li .bx-btn,
+.sys-author ul.bx-menu-custom li .bx-vote.bx-vote-stars,
+.bx-base-general-unit-meta ul.bx-menu-custom li .bx-base-general-unit-meta-item,
+.bx-base-general-unit-meta ul.bx-menu-custom li .bx-base-general-unit-meta-div,
+.bx-base-general-unit-meta ul.bx-menu-custom li .bx-btn,
+.bx-base-general-unit-meta ul.bx-menu-custom li .bx-vote.bx-vote-stars {
+    position: relative;
+    float: none;
+    display: inline-block;
+    vertical-align: middle;
+}
+
+.sys-author ul.bx-menu-custom li .bx-vote.bx-vote-stars,
+.bx-base-general-unit-meta ul.bx-menu-custom li .bx-vote.bx-vote-stars {
+    margin-top: 0px;
+    margin-bottom: 0px;
+}
+
+.sys-author ul.bx-menu-custom li .bx-metatags-keyword,
+.bx-base-general-unit-meta ul.bx-menu-custom li .bx-metatags-keyword {
+    margin: 0px;
+    line-height: normal;
+}

+ 726 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/groups/classes/BxBaseModGroupsModule.php

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

+ 86 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/groups/classes/BxBaseModGroupsSearchResult.php

@@ -0,0 +1,86 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModGroupsSearchResult extends BxBaseModProfileSearchResult
+{    
+    public function __construct($sMode = '', $aParams = array())
+    {
+        if (!isset($aParams['unit_views']))
+            $aParams['unit_views'] = array('gallery' => 'unit.html', 'showcase' => 'unit_with_cover_showcase.html');
+        parent::__construct($sMode, $aParams);
+    }
+
+    protected function addConditionsForPrivateContent($CNF, $oProfile, $aCustomGroup = array()) 
+    {
+        if(empty($CNF['OBJECT_PRIVACY_VIEW']))
+            return;
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+        if(!$oPrivacy)
+            return;
+
+        parent::addConditionsForPrivateContent($CNF, $oProfile, array_merge($aCustomGroup, $oPrivacy->getPartiallyVisiblePrivacyGroups()));
+    }
+
+    protected function _setAuthorConditions($sMode, $aParams, &$oProfileAuthor)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $oProfileAuthor = BxDolProfile::getInstance((int)$aParams['author']);
+        if (!$oProfileAuthor) 
+            return false;
+
+        $iProfileAuthor = $oProfileAuthor->id();
+        $this->aCurrent['restriction']['owner']['value'] = $iProfileAuthor;
+
+        if(!empty($aParams['per_page']))
+        	$this->aCurrent['paginate']['perPage'] = is_numeric($aParams['per_page']) ? (int)$aParams['per_page'] : (int)getParam($aParams['per_page']);
+
+        $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_JOINED_ENTRIES'] . '&profile_id={profile_id}';
+        $this->aCurrent['title'] = _t($CNF['T']['txt_all_entries_by_author']);
+        $this->aCurrent['rss']['link'] = 'modules/?r=' . $this->oModule->_oConfig->getUri() . '/rss/' . $sMode . '/' . $iProfileAuthor;
+
+        return true;
+    }
+
+    protected function _setFavoriteConditions($sMode, $aParams, &$oProfileAuthor)
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $oProfileAuthor = BxDolProfile::getInstance((int)$aParams['user']);
+        if (!$oProfileAuthor) 
+            return false;
+
+        $iProfileAuthor = $oProfileAuthor->id();
+        $oFavorite = $this->oModule->getObjectFavorite();
+        if(!$oFavorite || (!$oFavorite->isPublic() && $iProfileAuthor != bx_get_logged_profile_id())) 
+            return false;
+
+        $aConditions = $oFavorite->getConditionsTrack($CNF['TABLE_ENTRIES'], 'id', $iProfileAuthor);
+        if(!empty($aConditions) && is_array($aConditions)) {
+            if(empty($this->aCurrent['restriction']) || !is_array($this->aCurrent['restriction']))
+                $this->aCurrent['restriction'] = array();
+            $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $aConditions['restriction']);
+
+            if(empty($this->aCurrent['join']) || !is_array($this->aCurrent['join']))
+                $this->aCurrent['join'] = array();
+            $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $aConditions['join']);
+        }
+
+        $this->sBrowseUrl = 'page.php?i=' . $CNF['URI_JOINED_ENTRIES'] . '&profile_id={profile_id}';
+        $this->aCurrent['title'] = _t($CNF['T']['txt_all_entries_by_author']);
+        $this->aCurrent['rss']['link'] = 'modules/?r=' . $this->oModule->_oConfig->getUri() . '/rss/' . $sMode . '/' . $iProfileAuthor;
+
+        return true;
+    }
+}
+
+/** @} */

+ 190 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/notifications/classes/BxBaseModNotificationsConfig.php

@@ -0,0 +1,190 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseNotifications Base classes for Notifications like modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModNotificationsConfig extends BxBaseModGeneralConfig
+{
+    protected $_oDb;
+
+    protected $_aObjects;
+
+    protected $_aHandlerDescriptor;
+    protected $_sHandlersMethod;
+    protected $_aHandlers;
+    protected $_aHandlersHidden;
+
+    protected $_aPerPage;
+    protected $_aHtmlIds;
+	
+    protected $_sAnimationEffect;
+    protected $_iAnimationSpeed;
+
+    protected $_aPrivacyViewDefault;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aPrefixes = array();
+        $this->_aObjects = array(
+            'alert' => $this->_sName,
+
+            'privacy_view' => $this->_sName . '_privacy_view',
+
+            'conn_subscriptions' => 'sys_profiles_subscriptions'
+        );
+
+        $this->_aHandlerDescriptor = array();
+        $this->_sHandlersMethod = '';
+        $this->_aHandlersHidden = array();
+        $this->_aHandlers = array();
+
+        $this->_aJsClasses = array();
+        $this->_aJsObjects = array();
+
+        $this->_aPerPage = array();
+        $this->_aHtmlIds = array();
+
+        $this->_sAnimationEffect = 'fade';
+        $this->_iAnimationSpeed = 'slow';
+
+        $this->_aPrivacyViewDefault = array(
+            'object' => BX_DOL_PG_ALL,
+            'event' => BX_DOL_PG_ALL
+        );
+    }
+
+    public function init(&$oDb)
+    {
+        $this->_oDb = &$oDb;
+        $sOptionPrefix = $this->getPrefix('option');
+
+        $aHandlers = $this->_oDb->getHandlers();
+        foreach($aHandlers as $aHandler) {
+            if($aHandler['type'] === BX_BASE_MOD_NTFS_HANDLER_TYPE_INSERT && !empty($aHandler['content'])) {
+                $aContent = unserialize($aHandler['content']);
+                if(is_array($aContent) && !empty($aContent))
+                    $aHandler = array_merge($aHandler, $aContent);
+            }
+
+           $this->_aHandlers[$aHandler['alert_unit'] . (!empty($aHandler['alert_action']) ? '_' . $aHandler['alert_action'] :'')] = $aHandler;
+        }
+
+        $sHideTimeline = getParam($sOptionPrefix . 'events_hide');
+        if(!empty($sHideTimeline))
+            $this->_aHandlersHidden = explode(',', $sHideTimeline);
+    }
+
+    public function getHandlerDescriptor()
+    {
+    	return $this->_aHandlerDescriptor;
+    }
+
+    public function getHandlersMethod()
+    {
+    	return $this->_sHandlersMethod;
+    }
+
+    public function getHandlersUnitTitle($sUnit)
+    {
+        $sKey = '_' . $sUnit;
+        if(strcmp($sKey, _t($sKey)) === 0)
+            $sKey = $this->getPrefix('language') . '_alert_module_' . $sUnit;
+
+        return $sKey;
+    }
+
+    public function getHandlersActionTitle($sUnit, $sAction, $sType = '')
+    {
+        $sKeyMask = '_%s_alert_action_%s' . (!empty($sType) ? '_%s' : '');
+
+        $sKey = sprintf($sKeyMask, $sUnit, $sAction, $sType);
+        if(strcmp($sKey, _t($sKey)) === 0)
+            $sKey = sprintf($sKeyMask, trim($this->getPrefix('language'), '_'), $sAction, $sType);
+
+        return $sKey;
+    }
+
+    public function getHandler(&$aEvent)
+    {
+        $sHandler = $aEvent['type'] . '_' . $aEvent['action'];
+        if(!$this->isHandler($sHandler))
+            return false;
+
+        $aHandler = $this->getHandlers($sHandler);
+        if(empty($aHandler['module_name']) || empty($aHandler['module_class']) || empty($aHandler['module_method']))
+            return false;
+
+        return $aHandler;
+    }
+
+    public function isHandler($sKey = '')
+    {
+        return isset($this->_aHandlers[$sKey]);
+    }
+
+    public function getHandlers($sKey = '')
+    {
+        if($sKey == '')
+            return $this->_aHandlers;
+
+        return isset($this->_aHandlers[$sKey]) ? $this->_aHandlers[$sKey] : '';
+    }
+
+    public function getHandlersHidden()
+    {
+        return $this->_aHandlersHidden;
+    }
+
+    public function getPrivacyObject($sKey = '')
+    {
+    	if(!$this->isHandler($sKey))
+    		return false;
+
+    	$aHandler = $this->getHandlers($sKey);
+    	if(empty($aHandler) || !is_array($aHandler) || empty($aHandler['privacy']))
+    		return false;
+
+		return BxDolPrivacy::getObjectInstance($aHandler['privacy']);
+    }
+
+	public function getPerPage($sType = 'default')
+    {
+    	if(empty($sType))
+            return $this->_aPerPage;
+
+        return isset($this->_aPerPage[$sType]) ? $this->_aPerPage[$sType] : '';
+    }
+
+    public function getHtmlIds($sType, $sKey = '')
+    {
+        if(empty($sKey))
+            return isset($this->_aHtmlIds[$sType]) ? $this->_aHtmlIds[$sType] : array();
+
+        return isset($this->_aHtmlIds[$sType][$sKey]) ? $this->_aHtmlIds[$sType][$sKey] : '';
+    }
+
+    public function getAnimationEffect()
+    {
+        return $this->_sAnimationEffect;
+    }
+
+    public function getAnimationSpeed()
+    {
+        return $this->_iAnimationSpeed;
+    }
+
+    public function getPrivacyViewDefault($sType)
+    {
+        return isset($this->_aPrivacyViewDefault[$sType]) ? (int)$this->_aPrivacyViewDefault[$sType] : (int)BX_DOL_PG_ALL;
+    }
+}
+
+/** @} */

+ 104 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/classes/BxBaseModProfileDb.php

@@ -0,0 +1,104 @@
+<?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 based module database queries
+ */
+class BxBaseModProfileDb extends BxBaseModGeneralDb
+{
+    public function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    public function getContentInfoById ($iContentId)
+    {
+        $sQuery = $this->prepare ("SELECT `c`.*, `p`.`account_id`, `p`.`id` AS `profile_id`, `a`.`email` AS `profile_email`, `a`.`ip` AS `profile_ip`, `p`.`status` AS `profile_status` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` INNER JOIN `sys_profiles` AS `p` ON (`p`.`content_id` = `c`.`id` AND `p`.`type` = ?) INNER JOIN `sys_accounts` AS `a` ON (`p`.`account_id` = `a`.`id`) WHERE `c`.`id` = ?", $this->_oConfig->getName(), $iContentId);
+        return $this->getRow($sQuery);
+    }
+
+    public function resetContentPictureByFileId($iFileId, $sFieldPicture)
+    {
+        return $this->query("UPDATE `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` SET `" . $sFieldPicture . "` = 0 WHERE `" . $sFieldPicture . "` = :file", [
+    		'file' => $iFileId,
+        ]);
+    }
+
+    public function updateContentPictureById($iContentId, $iProfileId, $iPictureId, $sFieldPicture)
+    {
+    	$aBindings = array(
+    		'pic' => $iPictureId,
+    		'id' => $iContentId
+    	);
+
+        $sWhere = '';
+        if ($iProfileId) {
+        	$aBindings['author'] = $iProfileId;
+
+            $sWhere = " AND `author` = :author ";
+        }
+
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` SET `" . $sFieldPicture . "` = :pic WHERE `id` = :id" . $sWhere;
+        return $this->query($sQuery, $aBindings);
+    }
+
+    public function searchByTerm($sTerm, $iLimit)
+    {
+        if (!$this->_oConfig->CNF['FIELDS_QUICK_SEARCH'])
+            return array();
+
+		$aBindings = array(
+			'type' => $this->_oConfig->getName(),
+			'status' => BX_PROFILE_STATUS_ACTIVE
+		);
+
+        $sWhere = '';
+        foreach ($this->_oConfig->CNF['FIELDS_QUICK_SEARCH'] as $sField) {
+        	$aBindings[$sField] = $sTerm . '%';
+
+            $sWhere .= " OR `c`.`$sField` LIKE :" . $sField;
+        }
+
+        $sOrderBy = $this->prepareAsString(" ORDER BY `a`.`logged` DESC LIMIT ?", (int)$iLimit);
+
+        $sQuery = "SELECT `c`.`id` AS `content_id`, `p`.`account_id`, `p`.`id` AS `profile_id`, `p`.`status` AS `profile_status` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` 
+            INNER JOIN `sys_profiles` AS `p` ON (`p`.`content_id` = `c`.`id` AND `p`.`type` = :type) 
+            INNER JOIN `sys_accounts` AS `a` ON (`a`.`profile_id` = `p`.`id`) 
+            WHERE `p`.`status` = :status AND (0 $sWhere)" . $sOrderBy;
+        return $this->getAll($sQuery, $aBindings);
+    }
+
+    protected function _getEntriesBySearchIds($aParams, &$aMethod, &$sSelectClause, &$sJoinClause, &$sWhereClause, &$sOrderClause, &$sLimitClause)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(isset($aParams['search_params']['online'])) {
+            $aMethod['params'][1] = array_merge($aMethod['params'][1], array(
+            	'online_type' => $this->_oConfig->getName(),
+                'online_time' => (int)getParam('sys_account_online_time')
+            ));
+
+            $sJoinClause .= "
+            	LEFT JOIN `sys_profiles` AS `tp` ON `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_ID'] . "`=`tp`.`content_id` AND `tp`.`type`=:online_type 
+            	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` 
+            	";
+
+            $sWhereClause .= " AND `ta`.`profile_id`=`tp`.`id` AND `ts`.`date` > (UNIX_TIMESTAMP() - 60 * :online_time)";
+
+            unset($aParams['search_params']['online']);
+        }
+
+        parent::_getEntriesBySearchIds($aParams, $aMethod, $sSelectClause, $sJoinClause, $sWhereClause, $sOrderClause, $sLimitClause);        
+    }
+}
+
+/** @} */

+ 1553 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/classes/BxBaseModProfileModule.php

@@ -0,0 +1,1553 @@
+<?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;
+
+        $iContentId = is_numeric($aDataEntry) ? (int)$aDataEntry : $aDataEntry[$this->_oConfig->CNF['FIELD_ID']];
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $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;
+    }
+    
+    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();
+    }
+}
+
+/** @} */

+ 489 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/template/css/main.css

@@ -0,0 +1,489 @@
+@import url(../../../general/template/css/main.css);
+
+/* browse unit */
+
+.bx-base-pofile-unit {
+    width:184px;
+    width:11.5rem;
+    float:left;
+}
+
+.bx-base-pofile-unit-wo-info {
+    position: relative;
+    display: block;
+}
+
+.bx-search-result-block {
+    text-align:center;
+}
+
+.bx-base-pofile-unit .bx-def-unit-img,
+.bx-base-pofile-unit-wo-info .bx-def-unit-img,
+.bx-base-pofile-unit-with-cover .bx-def-unit-img {
+    position: relative;
+    overflow: visible;
+}
+
+.bx-def-unit-img .bx-base-pofile-unit-thumb {
+    background: rgba(255, 255, 255, 1.0);
+    background-clip: padding-box;
+    border-width: 2px;
+    border-color: rgba(255, 255, 255, 1.0);
+    box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2);
+}
+
+/* browse unit with cover */
+.bx-base-pofile-unit-cover {
+    display:block;
+
+    height: 10.0rem;
+
+    line-height:10.0rem;
+    text-align:center;
+
+    background-repeat:no-repeat;
+    background-position:center center;
+    -webkit-background-size:cover;
+    -moz-background-size:cover;
+    -o-background-size:cover;
+    background-size:cover;
+}
+
+.bx-base-pofile-unit-with-cover .sys-author ul.bx-menu-custom.bx-menu-hor,
+.bx-base-pofile-unit-with-cover .bx-base-general-unit-meta ul.bx-menu-custom.bx-menu-hor {
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+
+img.bx-base-pofile-unit-thumb {
+    display: block;
+
+    background-color: #ffffff;
+}
+
+p.bx-base-pofile-unit-thumb {
+    display: block;
+    margin: 0px;
+
+    line-height: 48px;
+    line-height: 3.0rem;
+    text-align: center;
+
+    font-size: 30px;
+    font-size: 1.875rem;
+    font-weight: 700;
+    color: #ffffff;
+}
+p.bx-base-pofile-unit-thumb.bx-def-icon-size {
+    line-height: 26px;
+    line-height: 1.625rem;
+
+    font-size: 20px;
+    font-size: 1.25rem;
+}
+p.bx-base-pofile-unit-thumb.bx-def-ava-size {
+    line-height: 96px;
+    line-height: 6rem;
+
+    font-size: 48px;
+    font-size: 3.0rem;
+    font-weight: 700;
+}
+.bx-base-pofile-unit-online {
+    position: absolute;
+    right: 2px;
+    bottom: 2px;
+    width: 10px;
+    height: 10px;
+    border: 1px solid #ffffff;
+    border-radius: 10px;
+    background-color: #7ed321;
+}
+.bx-base-pofile-unit-with-cover {
+    display:inline-block;
+    overflow:hidden;
+}
+.bx-base-pofile-unit-with-cover .bx-base-pofile-unit-cnt {
+    height: 100%;
+}
+.bx-base-pofile-unit-with-cover div.bx-def-unit-img {
+    display: inline-block;
+    vertical-align: middle;
+}
+.bx-base-pofile-unit-with-cover .bx-base-pofile-unit-online {
+    right: 9px;
+    bottom: 9px;
+}
+
+.bx-layout-column-thin .bx-base-pofile-unit-with-cover {
+    margin-left:0;
+    margin-right:0;
+}
+
+.bx-base-pofile-unit-title {
+    text-align:center;
+    white-space:nowrap;
+    text-overflow:ellipsis;
+    overflow:hidden;
+}
+
+.bx-base-pofile-unit-info-members {
+    float:left;
+    line-height:1.5rem;
+    vertical-align:middle;
+}
+
+.bx-base-pofile-unit-info-btn {
+    float:right;
+}
+
+
+/* --- Profile Cover --- */
+.bx-media-phone .bx-base-pofile-cover-wrapper {
+    padding-left: 0;
+    padding-right: 0;
+}
+.bx-base-pofile-cover {
+    position: relative;
+    min-height: 10rem;
+}
+.bx-media-phone .bx-base-pofile-cover {
+    border-left-width: 0;
+    border-right-width: 0;
+}
+div.bx-base-pofile-cover-image {
+    position: relative;
+    max-width: 100%;
+    height: 32rem;
+}
+a.bx-base-pofile-cover-image,
+a.bx-base-pofile-cover-image:hover {
+    position:absolute;
+    width:100%;
+    height:100%;
+    text-decoration:none;
+
+    background-color: #eee;
+    background-repeat: no-repeat;
+    background-position: center center;
+    -webkit-background-size: cover;
+    -moz-background-size: cover;
+    -o-background-size: cover;
+    background-size: cover;
+}
+a.bx-base-pofile-cover-image img {
+    position: relative;
+    display: block;
+    width: 100%;
+    height: 100%;
+
+    background-repeat: no-repeat;
+    background-position: center center;
+    -webkit-background-size: cover;
+    -moz-background-size: cover;
+    -o-background-size: cover;
+    background-size: cover;
+}
+.bx-base-pofile-cover-info {
+    position: relative;
+    width: 100%;
+}
+.bx-base-pofile-ci-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+.bx-media-phone .bx-base-pofile-ci-cnt {
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: center;
+    align-items: center;
+}
+
+div.bx-base-pofile-cover-thumb {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-end;
+    align-items: flex-end;
+    height: 7.0rem;
+    margin-right: 1rem;
+    margin-bottom: 1rem;
+}
+.bx-media-phone div.bx-base-pofile-cover-thumb {
+    margin-right: 0;
+    margin-bottom: 0;
+}
+div.bx-base-pofile-cover-thumb .bx-base-pofile-unit-online {
+    width: 16px;
+    height: 16px;
+    right: 25px;
+    bottom: 25px;
+
+    border-radius: 16px;
+}
+a.bx-base-pofile-cover-thumb,
+a.bx-base-pofile-cover-thumb img {
+    position: relative;
+    display: block;
+}
+a.bx-base-pofile-cover-thumb img {
+    background-color: #fff;
+}
+
+a.bx-base-pofile-cover-thumb p.bx-base-pofile-unit-thumb,
+a.bx-base-pofile-cover-thumb img.bx-base-pofile-unit-thumb {
+    background-color: rgba(255, 255, 255, 1.0);
+    background-clip: padding-box;
+    border: 5px solid rgba(255, 255, 255, 1.0);
+    box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2);
+}
+a.bx-base-pofile-cover-thumb p.bx-base-pofile-unit-thumb.bx-def-ava-big-size {
+    line-height: 200px;
+    line-height: 12.5rem;
+
+    font-size: 5.0rem;
+}
+
+.bx-base-pofile-cover-user {
+    -webkit-flex: 2 1 auto; 
+    -ms-flex:  2 1 auto; 
+    flex: 2 1 auto;
+
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-align-self: center;
+    align-self: center;
+
+    height: 100%;
+}
+
+.bx-base-pofile-cover-user h1.bx-def-unit-info {
+    position: relative;
+    display: block;
+
+    margin: 0;
+    padding: 0;
+
+    line-height:normal;
+    text-align: left;
+    text-shadow: none;
+    font-size: 1.5rem;
+    font-weight: 800;
+}
+
+.bx-base-pofile-cover-actions {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+
+    min-width: 3rem;
+    width: 100%;
+    height: 3.625rem;
+}
+
+.bx-base-pofile-cover-actions div.bx-base-general-entity-actions {
+    position: absolute;
+    width: 100%;
+}
+
+.bx-base-profile-view .bx-menu-main-bar {
+    overflow: visible;
+}
+
+.bx-menu-main-bar ul.bx-menu-main-submenu.bx-menu-more-auto,
+.bx-base-pofile-cover-actions ul.bx-menu-hor.bx-menu-more-auto {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: nowrap;
+    flex-wrap: nowrap;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+.bx-menu-main-bar ul.bx-menu-main-submenu.bx-menu-more-auto {
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item {
+    white-space: nowrap;
+}
+
+.bx-base-pofile-cover-actions .cmt,
+.bx-base-pofile-cover-actions .bx-view,
+.bx-base-pofile-cover-actions .bx-vote,
+.bx-base-pofile-cover-actions .bx-score,
+.bx-base-pofile-cover-actions .bx-favorite,
+.bx-base-pofile-cover-actions .bx-feature,
+.bx-base-pofile-cover-actions .bx-report,
+.bx-base-pofile-cover-actions .bx-tl-repost,
+.bx-base-pofile-cover-actions .bx-menu-btns-hor.bx-menu-object-sys_social_sharing,
+.bx-base-pofile-cover-actions .bx-base-general-entity-action {
+    position: relative;
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+
+    margin: 0;
+    padding: 0;
+}
+
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .cmt-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .cmt-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-view-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-view-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-vote-element-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-vote-counter-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-score-element-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-score-counter-holder, 
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-favorite-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-favorite-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-report-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-report-counter-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-element-holder,
+.bx-base-pofile-cover-actions .bx-menu-hor li.bx-menu-item .bx-tl-repost-counter-holder {
+    float: none;
+    display: inline-block;
+    vertical-align: top;
+}
+
+.bx-base-pofile-popup-wrapper {
+    position:relative;
+}
+
+.bx-base-pofile-popup-btn {
+    position:absolute;
+    top:0;
+    right:0;
+}
+
+/* flexbox */
+.bx-base-pofile-units-wrapper {
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+
+    /* makes all items on a row equal height */
+    -webkit-box-align: stretch;
+    -webkit-align-items: stretch;
+    -ms-flex-align: stretch;
+    align-items: stretch;
+
+    /* wrap onto the next line rather than stretching or shrinking */
+    -webkit-flex-wrap: wrap;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+
+    /* tells flexbox to start items from the left */
+    -webkit-box-pack: start;
+    -ms-flex-pack: start;
+    plex-pack: start;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+
+.bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-box-flex: 0;
+
+    -webkit-flex: 0 0 100%;
+    -ms-flex: 0 0 100%;
+    flex: 0 0 100%;
+
+    overflow:hidden;
+}
+
+/* showcase view */
+.bx-base-unit-showcase-wrapper {
+    display: block;
+    white-space: nowrap;
+    overflow: hidden;
+}
+.bx-base-unit-showcase-wrapper .bx-base-unit-showcase {
+	white-space: normal;
+}
+.bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(100%);
+}
+.bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+	display: inline-block;
+	vertical-align: top;
+
+    width: calc(16.6%);
+}
+
+/* MEDIA: tablet2 */
+
+.bx-media-tablet2 .bx-layout-column-wide .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover,
+.bx-media-tablet2 #sys_search_results .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover,
+.bx-media-tablet2 .bx-layout-column-fullwidth .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+
+.bx-media-tablet2 .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(50%);
+}
+.bx-media-tablet2 .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(100%);
+}
+
+.bx-media-tablet2 .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(10.0%);
+}
+.bx-media-tablet2 .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(16.6%);
+}
+
+/* MEDIA: desktop */
+
+.bx-media-desktop .bx-layout-column-wide .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+.bx-media-desktop #sys_search_results .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover,
+.bx-media-desktop .bx-layout-column-fullwidth .bx-base-pofile-units-wrapper .bx-base-pofile-unit-with-cover {
+    -webkit-flex: 0 0 33.25%;
+    -ms-flex: 0 0 33.25%;
+    flex: 0 0 33.25%;
+}
+
+.bx-media-desktop .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(33.3%);
+}
+.bx-media-desktop .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(100%);
+}
+.bx-media-desktop .bx-layout-column-half .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-showcase {
+    width:calc(50%);
+}
+
+.bx-media-desktop .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(6.25%);
+}
+.bx-media-desktop .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(25.0%);
+}
+.bx-media-desktop .bx-layout-column-half .bx-base-unit-showcase-wrapper .bx-base-pofile-unit-wo-info-showcase {
+    width:calc(10.0%);
+}
+
+
+
+/* view page */
+.bx-base-profile-entity-content,
+.bx-base-profile-entity-content p {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}

+ 6 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/profile/template/unit_meta_item.html

@@ -0,0 +1,6 @@
+<bx_if:span><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="bx-base-profile-unit-meta-item"><span class="bx-def-font-grayed __class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</span></span></bx_if:span>
+<bx_if:a><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="bx-base-profile-unit-meta-item"><a class="__class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a></span></bx_if:a>
+<bx_if:button><span class="bx-base-profile-unit-meta-item bx-def-padding-thd-leftright"><a class="bx-btn __class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a></span></bx_if:button>
+<bx_if:sbutton><span class="bx-base-profile-unit-meta-item bx-def-padding-thd-leftright"><a class="bx-btn bx-btn-small __class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a></span></bx_if:sbutton>
+<bx_if:custom><span class="bx-base-profile-unit-meta-item">__content__</span></bx_if:custom>
+<bx_if:nl></bx_if:nl>

+ 660 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/classes/BxBaseModTextTemplate.php

@@ -0,0 +1,660 @@
+<?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)
+    {
+        $aParams = array_merge(array(
+            'aHtmlIds' => $this->_oConfig->getHtmlIds()
+        ), $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];
+
+        return $this->entryAttachmentsByStorage($aStorages, $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);
+    }
+
+    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_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)
+        ));
+    }
+    
+    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;
+        switch($sStorage) {
+            case $CNF['OBJECT_STORAGE_PHOTOS']:
+                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']);
+                break;
+
+            case $CNF['OBJECT_STORAGE_FILES']:
+                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']);
+                break;
+        }
+
+        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);
+    }
+}
+
+/** @} */

+ 1 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/template/author.html

@@ -0,0 +1 @@
+<bx_include_base:../../../../template/author.html />

+ 235 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/template/css/main.css

@@ -0,0 +1,235 @@
+@import url(../../../general/template/css/main.css);
+
+/* unit */
+.bx-base-text-unit h2,
+.bx-base-text-unit h1 {
+    margin:0;
+}
+
+.bx-base-text-unit .sys-icon {
+    vertical-align:middle;
+}
+
+.bx-base-text-unit-cnt {
+	width: 100%;
+}
+
+.bx-base-text-unit-no-thumb,
+.bx-base-text-unit-thumb {
+    display:block;
+    overflow:hidden;    
+}
+
+.bx-base-text-unit-title,
+.bx-base-text-unit-summary {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.bx-base-text-unit-summary p {
+    margin:0;
+}
+
+.bx-base-text-unit-cover {
+	position: relative;
+}
+.bx-base-text-unit-text {
+	width: 100%;
+	overflow: hidden;
+    text-overflow: ellipsis;
+}
+.bx-base-text-unit-ext .bx-base-text-unit-text {
+	text-align: left;
+}
+
+.bx-base-text-unit-cover img {
+	position: relative;
+	display: block;
+
+	width: 100%;
+	height: 300px;
+    object-fit: cover;
+}
+
+.bx-base-text-unit-info {
+    white-space:nowrap;
+    overflow:hidden;
+    text-overflow:ellipsis;
+}
+
+.bx-base-text-unit-date {
+    white-space: nowrap;
+}
+
+.bx-base-text-unit-gallery .bx-base-text-unit-no-thumb,
+.bx-base-text-unit-gallery .bx-base-text-unit-thumb {
+    float:none;
+    display:block;
+    width:100%;
+
+    background-size:cover;
+    background-repeat:no-repeat;
+    background-position:center center;
+
+    line-height:1.25em;
+}
+
+.bx-base-text-unit-gallery h2 {
+    margin-bottom: 0;
+    max-height: 3.75em;
+    line-height: 1.25em;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    text-align: left;
+    overflow: hidden;
+}
+
+.bx-base-text-wrapper {
+    margin-bottom:0;
+}
+
+.bx-base-text-unit-ext .bx-base-text-unit-thumb {
+    float:left;
+}
+
+.bx-base-text-unit-strecher {
+    visibility: hidden;
+}
+
+
+/* flexbox */
+
+.bx-base-text-unit-gallery-wrapper {
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+
+    /* makes all items on a row equal height */
+    -webkit-box-align: stretch;
+    -webkit-align-items: stretch;
+    -ms-flex-align: stretch;
+    align-items: stretch;
+
+    /* wrap onto the next line rather than stretching or shrinking */
+    -webkit-flex-wrap: wrap;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+
+    /* tells flexbox to start items from the left */
+    -webkit-box-pack: start;
+    -ms-flex-pack: start;
+    flex-pack: start;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+
+.bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery {
+    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;
+}
+
+/* MEDIA: phone2 */
+
+.bx-media-phone2 .bx-layout-column-half .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery,
+.bx-media-phone2 .bx-layout-column-thin .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+
+.bx-media-phone2 .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase {
+   width:calc(50%);
+}
+
+/* MEDIA: tablet2 */
+
+.bx-media-tablet2 .bx-layout-column-wide .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery,
+.bx-media-tablet2 #sys_search_results .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery,
+.bx-media-tablet2 .bx-layout-column-fullwidth .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+
+.bx-media-tablet2 .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase {
+   width:calc(50%);
+}
+
+/* MEDIA: desktop */
+
+.bx-media-desktop .bx-layout-column-wide .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery {
+    -webkit-flex: 0 0 49.8%;
+    -ms-flex: 0 0 49.8%;
+    flex: 0 0 49.8%;
+}
+.bx-media-desktop #sys_search_results .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery,
+.bx-media-desktop .bx-layout-column-fullwidth .bx-base-text-unit-gallery-wrapper .bx-base-text-unit-gallery {
+    -webkit-flex: 0 0 33.25%;
+    -ms-flex: 0 0 33.25%;
+    flex: 0 0 33.25%;
+}
+
+.bx-media-desktop .bx-layout-column-thin .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase {
+   width:calc(100%);
+}
+
+.bx-media-desktop .bx-layout-column-half .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase,
+.bx-media-desktop .bx-layout-column-wide .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase {
+   width:calc(50%);
+}
+
+.bx-media-desktop #sys_search_results  .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase,
+.bx-media-desktop .bx-layout-column-fullwidth  .bx-base-unit-showcase-wrapper .bx-base-text-unit-showcase {
+   width:calc(33.3%);
+}
+
+
+    
+/* view page */
+.bx-content-description-after .bx-vote-counter-wrapper {
+    padding-top: 1rem;
+    line-height: 2.5rem;
+    font-size: 1.5rem;
+}
+
+.bx-base-text-entity-image img {
+    max-width: 100%;
+}
+
+.bx-base-text-entity-content,
+.bx-base-text-entity-content p,
+.bx-base-text-entity-content h1,
+.bx-base-text-entity-content h2,
+.bx-base-text-entity-content h3 {
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.bx-base-text-entity-content p:first-child,
+.bx-base-text-entity-content h2:first-child,
+.bx-base-text-entity-content h1:first-child {
+    margin-top:0;
+}
+
+.bx-base-text-entity-content p:last-child,
+.bx-base-text-entity-content h2:last-child,
+.bx-base-text-entity-content h1:last-child {
+    margin-bottom:0;
+}
+
+.bx-base-text-entity-actions-all .bx-base-text-entity-share,
+.bx-base-text-entity-actions-all .bx-menu-btns-hor {
+    display:inline-block;
+    vertical-align:top;
+}

+ 5 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/modules/base/text/template/unit_live_search.html

@@ -0,0 +1,5 @@
+<div class="bx-base-text-unit bx-def-unit bx-def-unit-live-search bx-def-margin-sec-bottom" data-ts="__ts__">
+    <bx_if:thumb><a href="__content_url__"><div class="bx-def-unit-img bx-def-thumb-size bx-def-round-corners bx-def-box bx-def-color-bg-box bx-def-box-sizing" style="background-image:url(__thumb_url__);"></div></bx_if:thumb>
+    <bx_if:no_thumb><a href="__content_url__"><div class="bx-def-unit-img"><div class="bx-def-unit-img-empty bx-def-thumb-size bx-def-round-corners bx-def-box bx-def-color-bg-box bx-def-box-sizing"><i class="sys-icon __module_icon__"></i></div></div></a></bx_if:no_thumb>
+    <div class="bx-def-unit-info bx-def-thumb-size-max-height bx-def-padding-sec-left"><a href="__content_url__">__title__</a><br /><span class="bx-def-fonr-grayed bx-def-font-small">__module_name__</span></div>
+</div>

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/plugins_public/modernizr.min.js


+ 42 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/searchKeyword.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+$sClass = 'BxTemplSearch';
+
+$sElsName = 'bx_elasticsearch';
+$sElsMethod = 'is_configured';
+if(BxDolRequest::serviceExists($sElsName, $sElsMethod) && BxDolService::call($sElsName, $sElsMethod) && !bx_get('cat') && !bx_get('type')) {
+     $oModule = BxDolModule::getInstance($sElsName);
+
+     bx_import('Search', $oModule->_aModule);
+     $sClass = 'BxElsSearch';
+}
+
+$oSearch = new $sClass(bx_get('section'));
+$oSearch->setLiveSearch(bx_get('live_search') ? 1 : 0);
+$oSearch->setMetaType(bx_process_input(bx_get('type')));
+$oSearch->setCategoryObject(bx_process_input(bx_get('cat')));
+
+$sCode = '';
+if (bx_get('keyword') !== false) {
+    $sCode = $oSearch->response();
+    if (!$sCode)
+        $sCode = $oSearch->getEmptyResult();
+}
+
+$oTemplate = BxDolTemplate::getInstance();
+$oTemplate->setPageNameIndex (BX_PAGE_DEFAULT);
+$oTemplate->setPageHeader (_t("_Search"));
+$oTemplate->setPageContent ('page_main_code', $oSearch->getForm() . ($sCode ? $oSearch->getResultsContainer($sCode) : ''));
+$oTemplate->getPageCode();
+
+/** @} */

BIN
upgrade/files/10.0.0.B1-10.0.0.B2/files/studio/template/images/icons/ui-location_radius.png


+ 55 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/_sub_header.html

@@ -0,0 +1,55 @@
+<bx_include_auto:_header.html />
+
+<div class="bx-main <bx_injection:injection_main_class />">
+
+	<bx_injection:top />
+
+	<div id="bx-toolbar" class="bx-def-z-index-nav">
+	
+	    <div class="bx-def-centered bx-def-padding-sec-leftright bx-def-page-width bx-def-box-sizing">
+	
+	        <div id="bx-toolbar-content" class="bx-clearfix">
+	
+	            <div id="bx-menu-toolbar-1-container">
+	                <bx_menu:sys_toolbar_site />
+	            </div>
+	
+	            <div id="bx-logo-container">
+	
+	                <bx_injection:injection_logo_before />
+	                    __main_logo__
+	                <bx_injection:injection_logo_after />
+	
+	            </div>
+	
+	            <div id="bx-menu-toolbar-2-container">
+	                <bx_menu:sys_toolbar_member />
+	            </div>
+	
+	        </div>
+	
+	    </div>
+	
+	</div>
+	
+	<bx_injection:injection_between_cover_logo />
+
+    __cover__
+	
+	<bx_injection:injection_between_cover_top_menu />
+
+        <bx_menu:sys_site_submenu />
+
+	<bx_injection:injection_between_content_menu />
+
+	<div id="bx-content-wrapper">
+
+		__informer__
+		
+		<div class="bx-page-wrapper bx-def-centered bx-def-padding-sec-leftright bx-def-page-width bx-def-box-sizing">
+		
+		    <div id="bx-content-container" class="bx-def-padding-sec-left bx-def-padding-sec-right">
+		
+		        <bx_injection:injection_content_before />
+		
+		        <div id="bx-content-main" class="bx-def-margin-top">

+ 17 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/author.html

@@ -0,0 +1,17 @@
+<div class="sys-author-info">
+    <div class="sys-author bx-def-unit absolute">
+        <div class="bx-def-unit-img">__author_unit__</div>
+        <div class="bx-def-unit-info bx-def-ui-cols bx-def-thumb-size-min-height bx-def-thumb-size-max-height bx-def-thumb-size-left-indent">
+            <div class="bx-def-unit-info-cnt bx-def-box-sizing">
+                <div class="bx-def-unit-line"><a class="bx-def-unit-info-title" href="__author_url__" title="__author_title_attr__">__author_title__</a></div>
+                <div class="bx-def-unit-line bx-def-font-grayed bx-def-font-small">__author_desc__</div>
+            </div>
+        </div>
+    </div>
+    <div class="sys-author-profile bx-def-margin-thd-topbottom">__author_profile_desc__</div>
+</div>
+<bx_if:addon>
+    <div class="bx-def-margin-sec-top">
+        __content__
+    </div>
+</bx_if:addon>

+ 3 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/author_desc.html

@@ -0,0 +1,3 @@
+<bx_repeat:items>
+    <span class="bx-def-ui-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><bx_if:text><span>__content__</span></bx_if:text><bx_if:link><a href="__link__">__content__</a></bx_if:link>
+</bx_repeat:items>

+ 24 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/chart_stats.html

@@ -0,0 +1,24 @@
+<div class="bx-chart-stats bx-def-font-align-center">
+    <div class="bx-chart-stats-graph">
+        <canvas id="bx_chart_stats_graph" width="100%"></canvas>
+	    <script type="text/javascript">
+	    	var oStatsChart = new Chart($("#bx_chart_stats_graph"), {
+	    		type: 'doughnut',
+	    		data: __chart_data__,
+	    		options: {
+	    		    legend: {
+	    		        position: 'bottom'
+	    		    },
+	    		    padding: {
+	    		        left: 0,
+	    		        right: 0,
+	    		        top: 0,
+	    		        bottom: 0
+	    		    },
+	    		    maintainAspectRatio: false
+	    		},
+	    	});
+	    	$("#bx_chart_stats_graph").height(450);
+	    </script>
+    </div>
+</div>

+ 1 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/comment_empty.html

@@ -0,0 +1 @@
+<li class="__style_prefix__-no__class__ bx-def-padding-top bx-def-margin-bottom">__content__</li>

+ 13 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/comment_reply_box.html

@@ -0,0 +1,13 @@
+<div class="__style_prefix__-reply__class__ bx-def-margin-bottom">
+    <div class="__style_prefix__-cont __style_prefix__-post-reply">
+        <div class="__style_prefix__-cont-cnt">
+            <bx_if:show_form_min>
+                <div class="__style_prefix__-body-min__class_body_min__">
+                    <div class="__style_prefix__-bm-user">__author_unit__</div>
+                    <div class="__style_prefix__-bm-content bx-def-thumb-size-min-height bx-def-thumb-size-left-indent bx-def-font-grayed">__placeholder__</div>
+                </div>
+            </bx_if:show_form_min>
+            <div class="__style_prefix__-body__class_body__">__form__</div>
+        </div>
+    </div>
+</div>

+ 21 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/create_post_form.html

@@ -0,0 +1,21 @@
+<div id="sys-create-post-form" class="sys-create-post-form">
+    <div class="sys-cpf-header">
+        <div class="sys-cpf-title bx-def-padding-leftright bx-def-border-bottom bx-def-box-sizing">__title__</div>
+        <div class="sys-cpf-submenu">
+            <div class="sys-cpf-submenu-cnt">__menu__</div>
+        </div>
+    </div>
+    <div class="sys-cpf-cnt bx-def-padding">
+        <div id="sys-cpf-form">
+            <div class="sys-cpf-form sys-cpf-default">
+                <div class="sys-cpf-user">__user_thumb__</div>
+                <div class="sys-cpf-content bx-def-thumb-size-min-height bx-def-thumb-size-left-indent bx-def-font-grayed">
+                    <div class="bx-def-thumb-size" onclick="__js_object__.getForm('__default__')">__placeholder__</div>
+                </div>
+            </div>
+            <div class="sys-cpf-form sys-cpf-__default__" style="display:none">__form__</div>
+            <div class="sys-cpf-close"><a class="bx-btn" href="javascript:void(0)" onclick="__js_object__.hideForm(this)"><i class="sys-icon times"></i></a></div>
+        </div>
+    </div>
+</div>
+__js_content__

+ 163 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/_remixins.less

@@ -0,0 +1,163 @@
+// https://github.com/christopher-ramirez/remixings
+// ===== REM UNIT MIXINS ====
+@base-font-size-px: 16; // root's font size. @base-font-size-px MUST ME AN INTEGER.
+
+// ===== FONT MIXINS =====
+.font-size(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    font-size: @value;
+    font-size: ~"@{rem-value}rem";    
+}
+
+.line-height(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    line-height: @value;
+    line-height: ~"@{rem-value}rem";
+}
+
+.text-indent(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    text-indent: @value;
+    text-indent: ~"@{rem-value}rem";
+}
+
+
+// ===== MARGIN MIXINS =====
+.margin(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    margin: @value;
+    margin: ~"@{rem-value}rem";
+}
+
+.margin(@vertical, @horizontal) {
+    @rem-vvalue: (unit(@vertical) / @base-font-size-px);
+    @rem-hvalue: (unit(@horizontal) / @base-font-size-px);
+    margin: @vertical @horizontal;
+    margin: ~"@{rem-vvalue}rem @{rem-hvalue}rem";
+}
+
+.margin-top(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    margin-top: @value;
+    margin-top: ~"@{rem-value}rem";
+}
+
+.margin-right(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    margin-right: @value;
+    margin-right: ~"@{rem-value}rem";
+}
+
+.margin-bottom(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    margin-bottom: @value;
+    margin-bottom: ~"@{rem-value}rem";
+}
+
+.margin-left(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    margin-left: @value;
+    margin-left: ~"@{rem-value}rem";
+}
+
+
+// ===== PADDING MIXINS =====
+.padding(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    padding: @value;
+    padding: ~"@{rem-value}rem";
+}
+
+.padding(@vertical, @horizontal) {
+    @rem-vvalue: (unit(@vertical) / @base-font-size-px);
+    @rem-hvalue: (unit(@horizontal) / @base-font-size-px);
+    padding: @vertical @horizontal;
+    padding: ~"@{rem-vvalue}rem @{rem-hvalue}rem";
+}
+
+.padding-top(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    padding-top: @value;
+    padding-top: ~"@{rem-value}rem";
+}
+
+.padding-right(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    padding-right: @value;
+    padding-right: ~"@{rem-value}rem";
+}
+
+.padding-bottom(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    padding-bottom: @value;
+    padding-bottom: ~"@{rem-value}rem";
+}
+
+.padding-left(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    padding-left: @value;
+    padding-left: ~"@{rem-value}rem";
+}
+
+
+// ===== POSITION MIXINS =====
+.height(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    height: @value;
+    height: ~"@{rem-value}rem";
+}
+
+.min-height(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    min-height: @value;
+    min-height: ~"@{rem-value}rem";
+}
+
+.max-height(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    max-height: @value;
+    max-height: ~"@{rem-value}rem";
+}
+
+.width(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    width: @value;
+    width: ~"@{rem-value}rem";
+}
+
+.min-width(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    min-width: @value;
+    min-width: ~"@{rem-value}rem";
+}
+
+.max-width(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    max-width: @value;
+    max-width: ~"@{rem-value}rem";
+}
+
+.top(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    top: @value;
+    top: ~"@{rem-value}rem";
+}
+
+.left(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    left: @value;
+    left: ~"@{rem-value}rem";
+}
+
+.right(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    right: @value;
+    right: ~"@{rem-value}rem";
+}
+
+.bottom(@value) {
+    @rem-value: (unit(@value) / @base-font-size-px);
+    bottom: @value;
+    bottom: ~"@{rem-value}rem";
+}
+

+ 32 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/account.css

@@ -0,0 +1,32 @@
+/*-- My Prpfiles: Create Profile --*/
+.bx-menu-object-sys_add_profile {
+    text-align: center;
+}
+
+
+/*-- My Prpfiles: Switch Profile --*/
+.sys-profile-switch-row {
+    position:relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+
+.sys-profile-switch-row-unit {
+    -webkit-flex: 1 1 auto;
+    flex: 1 1 auto;
+}
+.sys-profile-switch-row-unit .bx-def-unit {
+    margin:0;
+}
+
+.sys-profile-switch-row-control {
+    -webkit-flex: 0 2 auto;
+    flex: 0 2 auto;
+}
+.sys-profile-switch-row-control .bx-btn {
+    float: none;
+}

+ 786 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/common.css

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

+ 700 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/default.less

@@ -0,0 +1,700 @@
+@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 {
+    .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;
+}

+ 485 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/general.css

@@ -0,0 +1,485 @@
+
+.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.75rem;
+}
+
+.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;
+}

+ 34 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/media-desktop.css

@@ -0,0 +1,34 @@
+
+html.bx-media-desktop {
+    font-size:100%;
+}
+
+.bx-media-desktop .bx-def-media-desktop-hide {
+    display:none !important;
+}
+.bx-media-desktop .bx-def-media-desktop-show-block {
+    display:block;
+}
+
+
+.bx-media-desktop .bx-def-constraint-all *,
+.bx-media-desktop .bx-def-constraint-list ol, .bx-def-constraint-list ul,
+.bx-media-desktop .bx-def-constraint-pre pre,
+.bx-media-desktop .bx-def-constraint-object object,
+.bx-media-desktop .bx-def-constraint-iframe iframe,
+.bx-media-desktop .bx-def-constraint-img img {
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    max-width:900px !important;
+    overflow:hidden !important;
+}
+.bx-media-desktop .bx-def-constraint-pre pre {
+    overflow:scroll !important;
+}
+.bx-media-desktop .bx-def-constraint-img img  {
+    max-width:100% !important;
+    height: auto !important;
+}

+ 200 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/media-phone.css

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

+ 49 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/media-tablet.css

@@ -0,0 +1,49 @@
+
+html.bx-media-tablet {
+    font-size:100%;
+}
+
+.bx-media-tablet .bx-def-media-tablet-hide {
+    display:none !important;
+}
+.bx-media-tablet .bx-def-media-tablet-show-block {
+    display:block;
+}
+
+
+.bx-media-tablet .bx-def-constraint-all *,
+.bx-media-tablet .bx-def-constraint-list ol, .bx-def-constraint-list ul,
+.bx-media-tablet .bx-def-constraint-pre pre,
+.bx-media-tablet .bx-def-constraint-object object,
+.bx-media-tablet .bx-def-constraint-iframe iframe,
+.bx-media-tablet .bx-def-constraint-img img {
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    max-width:500px !important;
+    overflow:hidden !important;
+}
+.bx-media-tablet .bx-def-constraint-pre pre {
+    overflow:scroll !important;
+}
+
+
+/* default.css */
+
+.bx-media-tablet .bx-def-page-width {
+    width:auto;
+}
+
+
+/* page layouts */
+.bx-media-tablet .bx-layout-wrapper.bx-layout-top-area-bcb .bx-layout-col {
+    float:none !important;
+    width:auto !important;
+}
+.bx-media-tablet .bx-layout-wrapper.bx-layout-top-area-bcb .bx-layout-col .bx-layout-cell {
+	padding-left: 0 !important;
+	padding-right: 0 !important;
+}
+

+ 843 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/menu.css

@@ -0,0 +1,843 @@
+/* --------------- 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;
+}
+
+/* --------------- 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-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;
+}

+ 10 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/search_extended.css

@@ -0,0 +1,10 @@
+/*-- Search extended custom fields --*/
+.bx-form-input-wrapper-location-radius > div{
+    display:inline-block;
+}
+.bx-form-input-wrapper-location-radius > div:first-child{
+    width:70%;
+}
+.bx-form-input-wrapper-location-radius > div:last-child{
+    width:29%;
+}

+ 169 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/css/vote.css

@@ -0,0 +1,169 @@
+/*--- Comment Plused By ---*/
+div.bx-vote-by-list {}
+div.bx-vote-by-list-cnt {
+	max-height: 200px;
+	max-height: 12.5rem;
+	min-width: 220px;
+	min-width: 13.75rem;
+	overflow: auto;
+}
+div.bx-vote-by-list div.bx-base-pofile-unit {
+	float: none;
+	margin-right: 0px;
+	margin-bottom: 0px;
+}
+
+/*--- Vote Element Styles ---*/
+div.bx-vote div.bx-vote-element-holder,
+div.bx-vote div.bx-vote-counter-holder {
+	position: relative;
+	float: left;
+}
+
+/*--- Likes based Element Styles ---*/
+.bx-vote-likes .bx-vote-element-holder,
+.bx-vote-likes .bx-vote-counter-holder {
+	line-height: 20px;
+	line-height: 1.25rem;
+}
+
+.bx-vote-likes-button .bx-vote-element-holder,
+.bx-vote-likes-button .bx-vote-counter-holder {
+	line-height: 36px;
+	line-height: 2.2rem;
+}
+
+.bx-vote-likes-button-small .bx-vote-element-holder,
+.bx-vote-likes-button-small .bx-vote-counter-holder {
+	line-height: 24px;
+	line-height: 1.6rem;
+}
+
+.bx-vote-likes .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled,
+.bx-vote-likes .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled .sys-icon {
+	color: #ddd;
+}
+
+.bx-vote-likes-button .bx-vote-counter-holder,
+.bx-vote-likes-button-small .bx-vote-counter-holder {
+	font-size: 1.5rem;
+}
+
+.bx-vote-likes .bx-vote-counter-holder.bx-btn-border {
+    border-width: 0px;
+}
+
+/*--- Reactions based Element Styles ---*/
+.bx-vote-reactions .bx-vote-element-holder,
+.bx-vote-reactions .bx-vote-counter-holder {
+	line-height: 20px;
+	line-height: 1.25rem;
+}
+
+.bx-vote-reactions-button .bx-vote-element-holder,
+.bx-vote-reactions-button .bx-vote-counter-holder {
+	line-height: 36px;
+	line-height: 2.2rem;
+}
+
+.bx-vote-reactions-button-small .bx-vote-element-holder,
+.bx-vote-reactions-button-small .bx-vote-counter-holder {
+	line-height: 24px;
+	line-height: 1.6rem;
+}
+
+.bx-vote-reactions .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled,
+.bx-vote-reactions .bx-vote-element-holder .bx-vote-do-vote.bx-vote-disabled .sys-icon {
+	color: #ddd;
+}
+
+.bx-vote-reactions-button .bx-vote-counter-holder,
+.bx-vote-reactions-button-small .bx-vote-counter-holder {
+	font-size: 1.5rem;
+}
+
+.bx-vote-reactions .bx-vote-counter-holder.bx-btn-border {
+    border-width: 0px;
+}
+
+.bx-vote-counter-wrapper-reactions .bx-vote-counter {
+    display: inline-block;
+}
+.bx-vote-counter-wrapper-reactions .bx-vote-counter.bx-vc-hidden {
+    display: none;
+}
+.bx-vote-counter-wrapper-reactions .bx-vote-counter i,
+.bx-vote-counter-wrapper-reactions .bx-vote-counter span {
+    vertical-align: middle;
+}
+
+/*--- Stars based Element Styles ---*/
+.bx-vote-stars,
+.bx-vote-stars .bx-vote-do,
+.bx-vote-stars .bx-vote-element-holder,
+.bx-vote-stars .bx-vote-counter-holder {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+
+    line-height: 16px;
+    line-height: 1rem;
+    font-size: 16px;
+    font-size: 1rem;
+}
+
+.bx-vote-stars .bx-vote-counter-holder {
+    border-width: 0px;
+}
+
+
+/*--- Stars Styles ---*/
+div.bx-vote-element {
+    position: relative;
+    display: inline-block;
+}
+div.bx-vote-underlay {}
+div.bx-vote-slider {
+    position: absolute;
+    z-index: 1;
+    top: 0px;
+    left: 0px;
+    white-space: nowrap;
+    overflow: hidden;
+}
+.bx-vote-stars .bx-vote-element-holder a.bx-vote-button,
+.bx-vote-stars .bx-vote-element-holder i.bx-vote-star,
+.bx-vote-stars .bx-vote-element-holder span.bx-vote-star {
+    position: relative;
+    display: inline-block;
+    vertical-align: top;
+    height: 16px;
+    width: 16px;
+    margin: 0;
+    padding: 0;
+    text-align: center;
+}
+div.bx-vote-underlay i.bx-vote-star,
+div.bx-vote-underlay span.bx-vote-star {
+    color: rgba(255, 204, 102, 1);
+}
+div.bx-vote-slider i.bx-vote-star,
+div.bx-vote-slider span.bx-vote-star,
+div.bx-vote-buttons a:hover i.bx-vote-star,
+div.bx-vote-buttons a:hover span.bx-vote-star {
+    color: rgba(255, 100, 0, 1);
+}
+
+div.bx-vote-buttons {
+    position: absolute;
+    z-index: 2;
+    top: 0px;
+    left: 0px;
+}
+div.bx-vote-buttons a:hover {
+	text-decoration: none;
+}
+div.bx-vote-buttons i.bx-vote-star,
+div.bx-vote-buttons span.bx-vote-star {
+	color: transparent;
+}

+ 8 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/form_field_location_radius.html

@@ -0,0 +1,8 @@
+<div class="bx-form-input-wrapper-location-radius">
+    <div>
+        __location_input__
+    </div>
+    <div>
+        __radius_input__
+    </div>
+</div>

+ 91 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/grid.html

@@ -0,0 +1,91 @@
+<div id="__id_wrap__">
+    <bx_if:display_header>
+        <div class="bx-grid-header">
+        	<div class="bx-grid-header-cnt bx-def-padding-sec-bottom bx-clearfix">
+	            <bx_if:filter>
+	                <div class="bx-grid-header-controls-filter bx-def-margin-thd-top">
+	                    __controls__
+	                </div>  
+	            </bx_if:filter>
+	            <bx_if:actions_independent>
+	                <div class="bx-grid-header-controls-independent bx-def-margin-thd-neg">
+	                    __actions_independent__
+	                </div>
+	            </bx_if:actions_independent>
+			</div>
+        </div>
+    </bx_if:display_header>
+    <div class="bx-grid-table-wrapper2 bx-grid-table-hide-shadow-before __class_table_wrapper__">
+        <div id="__id_cont__" class="bx-grid-table-wrapper">
+            <table id="__id_table__" class="bx-grid-table">
+                <bx_if:display_head>
+                    <thead>
+                        <tr class="bx-grid-table-row-hr">
+                            <td colspan="__columns__"><hr class="bx-def-hr" /></td>
+                        </tr>                    
+                        <tr>
+                            <bx_repeat:row_header>
+                                __header_cell__
+                            </bx_repeat:row_header>
+                        </tr>
+                        <tr class="bx-grid-table-row-hr">
+                            <td colspan="__columns__"><hr class="bx-def-hr" /></td>
+                        </tr>
+                    </thead>
+                </bx_if:display_head>
+                <tbody>
+                        <bx_repeat:rows_data>
+                            <tr id="__id_row__" class="__row_class__">
+                                __row__
+                            </tr>
+                        </bx_repeat:rows_data>
+                </tbody>
+            </table>
+        </div>
+    </div>
+    <bx_if:display_footer>
+        <div class="bx-grid-footer ">
+            <hr class="bx-def-hr" />
+            <div class="bx-grid-footer-cnt bx-def-padding-sec-top bx-def-margin-thd-neg bx-clearfix">
+                <bx_if:actions_bulk>
+                    <div style="float:left;">
+                        __actions_bulk__
+                    </div>
+                </bx_if:actions_bulk>
+    
+                <div class="bx-def-margin-thd" style="float:right;">
+                    __paginate__
+                </div>
+            </div>
+        </div>
+    </bx_if:display_footer>    
+</div>
+
+<script>
+    $(document).ready(function() {
+        if (typeof(glGrids) == 'undefined')
+            glGrids = {};
+        if (undefined == glGrids.__object__) {
+            glGrids.__object__ = new BxDolGrid('__object__', {
+                sortable: __sortable__,
+                sorting: __sorting__,
+                paginate_get_start: '__paginate_get_start__',
+                paginate_get_per_page: '__paginate_get_per_page__',
+                start: __start__,
+                per_page: __per_page__,
+                filter: '__filter__',
+                filter_get: '__filter_get__',
+                order_field: '__order_field__',
+                order_dir: '__order_dir__',
+                order_get_field: '__order_get_field__',
+                order_get_dir: '__order_get_dir__',
+                text_search_input: '<bx_text_js:_sys_grid_search />',
+                popup_options: __popup_options__,
+                query_append: __query_append__,
+                confirm_messages: __confirm_messages__,
+                columns: __columns__
+            });
+        }
+    });
+</script>
+

+ 11 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/menu_footer.html

@@ -0,0 +1,11 @@
+<ul id="bx-menu-footer" class="bx-menu-hor bx-menu-object-__object__ bx-clearfix">
+    <bx_repeat:menu_items>
+        <li class="bx-def-margin-right-auto __class_add__">
+            <a href="__link__" target="__target__" onclick="__onclick__" title="__title_attr__">
+                <bx_if:image><img title="__title_attr__" src="__icon_url__" /></bx_if:image>
+                <bx_if:icon><i class="sys-icon __icon__"></i></bx_if:icon>
+                <bx_if:title>__title__</bx_if:title>
+            </a>
+        </li>
+    </bx_repeat:menu_items>
+</ul>

+ 9 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/menu_main_submenu_bar.html

@@ -0,0 +1,9 @@
+<div id="bx-menu-main-bar-wrapper">
+    <div id="bx-menu-main-bar" class="bx-def-color-bg-sec">
+        <div class="bx-def-centered bx-def-padding-sec-leftright bx-def-box-sizing">
+            <bx_injection:injection_top_menu_before />
+            __code__
+            <bx_injection:injection_top_menu_after />
+        </div>
+    </div>
+</div>

+ 24 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/menu_profile_stats.html

@@ -0,0 +1,24 @@
+<ul class="bx-menu-profile-stats bx-menu-ver bx-menu-object-__object__ bx-def-font-middle bx-clearfix">
+    <bx_repeat:menu_items>
+        <li class="bx-menu-item-__name__ bx-def-round-corners bx-def-hr bx-def-color-bg-hl-hover __class_add__">
+            <div class="bx-menu-item-cnt bx-def-padding-thd-topbottom bx-def-padding-sec-leftright">
+                <a href="__link__" target="__target__" onclick="__onclick__">
+                    <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 class="bx-def-margin-left-auto">__title__</span>
+                </a>
+                <bx_if:addon>
+                    <div class="bx-menu-addon bx-def-padding-sec-left">
+                        <div class="bx-menu-addon-cnt bx-def-label menu">__addon__</div>
+                    </div>
+                </bx_if:addon>
+            </div>
+        </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>

+ 48 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/page_22.html

@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="__lang__">
+<head>
+    <bx_injection:injection_head_begin />
+
+    <meta http-equiv="Content-Type" content="text/html; charset=__page_charset__" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />
+    <title>__page_header__</title>
+    <base href="<bx_url_root />" />
+
+    __meta_info__
+    __page_robots__
+
+    <meta http-equiv="Content-Style-Type" content="text/css" />
+
+    <bx_include_css_styles />
+    <bx_include_css_system />
+    <bx_include_css />
+    <style>
+        body.bx-def-color-bg-page {
+            overflow:hidden;
+            background-color:#000;
+        }
+    </style>
+
+    __dol_images__
+    __dol_lang__
+    __dol_options__
+
+    <bx_include_js_system />
+    <bx_include_js />
+
+    <bx_include_auto:_header_js.html />
+    <bx_include_auto:_header_js_spinner.html />
+    <bx_include_auto:_header_js_custom.html />
+
+    __extra_js__
+
+    __system_injection_head__
+    <bx_injection:injection_head />
+    <bx_include_auto:_header_meta.html />
+</head>
+<body class="bx-def-font bx-def-color-bg-page bx-def-image-bg-page bx-page-iframe" dir="__lang_direction__" <bx_injection:injection_body /> >
+    __system_injection_header__
+    <bx_injection:injection_header />
+    __page_main_code__
+</body>
+</html>

+ 21 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/profile_stats.html

@@ -0,0 +1,21 @@
+<div class="bx-profile-stats bx-def-border-radius-block">
+    <div class="bx-profile-stats-cnt bx-def-padding">
+	<div class="bx-ps-user">
+            <div class="bx-ps-user-cnt bx-def-margin-sec-leftright">
+                <div class="bx-ps-user-img">
+                    __profile_unit__
+                </div>
+                <div class="bx-ps-user-info">
+                    <div class="bx-ps-user-info-cnt bx-def-padding-left bx-def-box-sizing">
+                        <div class="bx-ps-user-line bx-def-font-h2"><a href="__profile_url__" title="__profile_title_attr__">__profile_title__</a></div>
+                        <div class="bx-ps-user-line bx-def-margin-sec-top">
+                            <div class="bx-def-label"><i class="sys-icon __profile_acl_icon__"></i><span class="bx-def-padding-sec-left">__profile_acl_title__</span></div>
+                        </div>
+                        <!--<div class="bx-def-unit-line"><a href="__profile_edit_url__"><bx_text:_sys_edit_profile /></a></div>-->
+                    </div>
+                </div>
+            </div>
+	</div>
+	<div class="bx-ps-menu bx-def-margin-top">__menu__</div>
+    </div>
+</div>

+ 13 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/profile_switch_row.html

@@ -0,0 +1,13 @@
+<bx_repeat:row>
+
+<hr class="bx-def-hr-auto" />
+<div class="sys-profile-switch-row bx-def-padding-leftright bx-def-padding-sec-topbottom">
+    <div class="sys-profile-switch-row-unit">__unit__</div>
+    <div class="sys-profile-switch-row-control">
+        <bx_if:inactive>
+            <a class="bx-btn bx-btn-small" href="__url_switch__">__button_title__</i></a>
+        </bx_if:inactive>
+    </div>
+</div>
+
+</bx_repeat:row>

+ 1115 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseCmts.php

@@ -0,0 +1,1115 @@
+<?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 $_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->_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,
+        );
+
+        $this->addCssJs();
+        return $this->_oTemplate->_wrapInTagJsCode("if(window['" . $this->_sJsObjName . "'] == undefined) var " . $this->_sJsObjName . " = new BxDolCmts(" . 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()
+                );
+            }
+        }
+
+        return $this->_oTemplate->parseHtmlByName('comment.html', 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
+                )
+            ),
+            '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())
+    {
+        if(!$this->isPostReplyAllowed())
+            return '';
+
+        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->isPostReplyAllowed();
+
+        //--- 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'] : '';
+        $bFormMin = $iCmtParentId == 0 && isset($aDp['min_post_form']) ? $aDp['min_post_form'] : false;
+
+        $sPositionSystem = $this->_aSystem['post_form_position'];
+        if(!empty($sPosition) && $sPositionSystem != $sPosition)
+            return '';
+
+        $sClass = '';
+        if(!empty($sPosition))
+            $sClass .= ' ' . $this->_sStylePrefix . '-reply-' . $sPosition;
+        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('_cmt_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);
+
+        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())
+    {
+        $bDynamic = isset($aDp['dynamic_mode']) && (bool)$aDp['dynamic_mode'];
+
+        $oForm = $this->_getForm(BX_CMT_ACTION_POST, $iCmtParentId);
+        $oForm->aInputs['cmt_parent_id']['value'] = $iCmtParentId;
+        $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) {
+                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();
+
+                $this->isPostReplyAllowed(true);
+
+                if ($this->_sMetatagsObj) {
+                    $oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj);
+                    $oMetatags->metaAdd($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId), $sCmtText);
+                }
+
+                $this->onPostAfter($iCmtId);
+
+                return array('id' => $iCmtId, 'parent_id' => $iCmtParentId);
+            }
+
+            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->_oQuery->getCommentSimple ((int)$this->getId(), $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) {
+
+                $this->isEditAllowed($aCmt, true);
+
+                if ($this->_sMetatagsObj) {
+                    $oMetatags = BxDolMetatags::getObjectInstance($this->_sMetatagsObj);
+                    $oMetatags->metaAdd($this->_oQuery->getUniqId($this->_aSystem['system_id'], $iCmtId), $sCmtText);
+                }
+
+                $this->onEditAfter($iCmtId);
+
+                return array('id' => $iCmtId, 'text' => $this->_prepareTextForOutput($sCmtText, $iCmtId));
+            }
+
+            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
+                )
+            ),
+        );
+    }
+}
+
+/** @} */

+ 393 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseConfig.php

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

+ 1741 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseFormView.php

@@ -0,0 +1,1741 @@
+<?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 = '';
+        if ($sCaption)
+            $sCaptionCode = '<div class="bx-form-caption' . $sClassOneLineCaption . '">' . $sCaption . $sRequired . '</div>';
+        else
+            $sInput .= $sRequired;
+
+        $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 '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, ',');
+        }
+
+        return $this->oTemplate->parseHtmlByName('form_field_custom_suggestions.html', array(
+            'id' => $aInput['name'] . time() . mt_rand(0, 100),
+            'url_get_recipients' => $aInput['ajax_get_suggestions'],
+            'name' => $aInput['name'],
+			'b_img' => isset($aInput['custom']['b_img']) ? (int)$aInput['custom']['b_img'] : 1,
+			'only_once' => isset($aInput['custom']['only_once']) ? 1 : 0,
+			'on_select' => isset($aInput['custom']['on_select']) ? $aInput['custom']['on_select']: 'null',
+            'placeholder' => bx_html_attribute(isset($aInput['placeholder']) ? $aInput['placeholder'] : _t('_sys_form_paceholder_profiles_suggestions'), BX_ESCAPE_STR_QUOTE),
+            'vals' => $sVals,
+        ));
+    }
+
+    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;
+
+        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;
+    }
+}
+
+/** @} */

+ 691 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseGrid.php

@@ -0,0 +1,691 @@
+<?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
+ * @{
+ */
+
+/**
+ * Grid representation.
+ * @see BxDolGrid
+ */
+class BxBaseGrid extends BxDolGrid
+{
+    protected $_oTemplate;
+    protected $_aPopupOptions = false;
+    protected $_aQueryAppend = false;
+    protected $_aQueryAppendExclude = false; // an array of keys which shouldn't be pathed in http requests, but can be stored (used) in 'Query Append' array.
+    protected $_aQueryReset = false;
+    protected $_aConfirmMessages = false;
+    protected $_bSelectAll = false;
+    protected $_isDisplayPopupOnTextOverflow = true;
+
+    public function __construct ($aOptions, $oTemplate)
+    {
+        parent::__construct ($aOptions);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_aQueryAppend = array();
+        $this->_aQueryAppendExclude = array();
+
+        $this->_aQueryReset = array($this->_aOptions['filter_get'], $this->_aOptions['order_get_field'], $this->_aOptions['order_get_dir'], $this->_aOptions['paginate_get_start'], $this->_aOptions['paginate_get_per_page']);
+    }
+
+    public function performActionDisplay()
+    {
+        require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+        echoJson(array('grid' => $this->getCode(false)));
+    }
+
+    public function performActionReorder()
+    {
+        $this->_replaceMarkers ();
+
+        $aOrder = bx_get($this->_sObject . '_row');
+        $iOrder = 0;
+        foreach ($aOrder as $mixedId)
+            $this->_updateOrder($mixedId, ++$iOrder);
+
+        echoJson(array());
+    }
+
+    public function performActionDelete()
+    {
+        $this->_replaceMarkers ();
+
+        $iAffected = 0;
+        $aIds = bx_get('ids');
+        if (!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            exit;
+        }
+
+        foreach ($aIds as $mixedId)
+            $iAffected += $this->_delete($mixedId) ? 1 : 0;
+
+        echo echoJson(array_merge(
+            array(
+                'grid' => $this->getCode(false),
+            ),
+            $iAffected ? array() : array('msg' => _t("_sys_grid_delete_failed"))
+        ));
+    }
+
+    public function performActionEnable()
+    {
+        $this->_replaceMarkers ();
+
+        $aIds = bx_get('ids');
+        if (!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            exit;
+        }
+
+        $aAffectedIds = array ();
+        foreach ($aIds as $mixedId)
+            if ($this->_enable($mixedId, (int)bx_get('checked')))
+                $aAffectedIds[] = $mixedId;
+
+        $sAction = (int)bx_get('checked') ? 'enable' : 'disable';
+        echo echoJson(array($sAction => $aAffectedIds));
+    }
+
+    public function getCode ($isDisplayHeader = true)
+    {
+        $this->_replaceMarkers ();
+
+        if ($isDisplayHeader && empty($this->_aOptions['paginate_url'])) {
+            // reset page query params if grid is just initialized and it uses AJAX paginate
+            $this->resetQueryParams();
+        }
+
+        $sPaginate = '';
+        $aData = array();
+
+        $sIdWrapper = 'bx-grid-wrap-' . $this->_sObject;
+        $sIdContainer = 'bx-grid-cont-' . $this->_sObject;
+        $sIdTable = 'bx-grid-table-' . $this->_sObject;
+
+        $sFilter = bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['filter_get'])));
+        $sOrderField = bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['order_get_field'])));
+        $sOrderDir = 0 === strcasecmp('desc', bx_get($this->_aOptions['order_get_dir'])) ? 'DESC' : 'ASC';
+
+        if ($this->_aOptions['paginate_get_start'])
+            $iStart = (int)bx_get($this->_aOptions['paginate_get_start']);
+        else
+            $iStart = 0;
+
+        if ($this->_aOptions['paginate_get_per_page'] && (int)bx_get($this->_aOptions['paginate_get_per_page']) > 0)
+            $iPerPage = (int)bx_get($this->_aOptions['paginate_get_per_page']);
+        elseif ($this->_aOptions['paginate_per_page'])
+            $iPerPage = (int)$this->_aOptions['paginate_per_page'];
+        else
+            $iPerPage = 10;
+
+        if ($this->_aOptions['paginate_get_start']) {
+
+            $aData = $this->_getData ($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage + 1);
+
+            $sPageUrl = false;
+            if (!empty($this->_aOptions['paginate_url'])) {
+
+                $sPageUrl = $this->_aOptions['paginate_url'];
+
+                $aParamsAppend = array();
+                if ($sFilter) {
+                    $aParamsAppend['filter'] = bx_process_input(bx_get($this->_aOptions['filter_get']));
+                }
+                if ($sOrderField) {
+                    $aParamsAppend['order_field'] = bx_process_input(bx_get($this->_aOptions['order_get_field']));
+                    $aParamsAppend['order_dir'] = bx_process_input(bx_get($this->_aOptions['order_get_dir']));
+                }
+                if ($aParamsAppend)
+                    $sPageUrl = bx_append_url_params($sPageUrl, $aParamsAppend);
+            }
+
+            $aPaginateParams = array(
+                'start' => $iStart,
+                'per_page' => $iPerPage,
+                'page_url' =>  $sPageUrl ? $sPageUrl : "javascript:glGrids." . $this->_sObject . ".reload('{start}'); void(0);",
+            );
+
+            $oPaginate = new BxTemplPaginate($aPaginateParams, $this->_oTemplate);
+            $oPaginate->setNumFromDataArray($aData);
+
+            if (isset($this->_aOptions['paginate_simple']) && false !== $this->_aOptions['paginate_simple'])
+                $sPaginate = $oPaginate->getSimplePaginate($this->_aOptions['paginate_simple']);
+            else
+                $sPaginate = $oPaginate->getPaginate();
+
+        } else {
+
+            $aData = $this->_getData ($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+
+        }
+
+        if((empty($aData) || !is_array($aData)) && isset($this->_aBrowseParams['empty_message']) && !(bool)$this->_aBrowseParams['empty_message'])
+            return '';
+
+        $sPopupOptions = '{}';
+        if (!empty($this->_aPopupOptions) && is_array($this->_aPopupOptions))
+            $sPopupOptions = json_encode($this->_aPopupOptions);
+
+        $aQueryAppend = array_merge(is_array($this->_aQueryAppend) ? $this->_aQueryAppend : array(), is_array($this->_aMarkers) ? $this->_aMarkers : array());
+        if(!empty($this->_aQueryAppendExclude) && is_array($this->_aQueryAppendExclude))
+            $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExclude));
+
+        $sQueryAppend = '{}';
+        if (!empty($aQueryAppend) && is_array($aQueryAppend))
+            $sQueryAppend = json_encode($aQueryAppend);
+
+        $sConfirmMessages = '{}';
+        if (!empty($this->_aConfirmMessages) && is_array($this->_aConfirmMessages))
+            $sConfirmMessages = json_encode($this->_aConfirmMessages);
+
+		$iColumns = count($this->_aOptions['fields']);
+		$aVarsHead = $this->_getRowHead();
+
+        $aVars = array (
+            'object' => $this->_sObject,
+            'id_table' => $sIdTable,
+            'id_cont' => $sIdContainer,
+            'id_wrap' => $sIdWrapper,
+            'class_table_wrapper' => $this->_aOptions['responsive'] ? 'bx-grid-table-wrapper-responsive' : '',
+            'sortable' => empty($this->_aOptions['field_order']) ? 0 : 1,
+            'sorting' => empty($this->_aOptions['sorting_fields']) ? 0 : 1,
+            'sorting_field' => $sOrderField,
+            'sorting_dir' => $sOrderDir,
+        	'bx_if:display_head' => array(
+        		'condition' => !empty($aVarsHead),
+        		'content' => array(
+        			'bx_repeat:row_header' => $aVarsHead,
+        			'columns' => $iColumns,
+        		)
+        	),
+            'bx_repeat:rows_data' => $this->_getRowsDataDesign ($aData),
+            'paginate_get_start' => $this->_aOptions['paginate_get_start'],
+            'paginate_get_per_page' => $this->_aOptions['paginate_get_per_page'],
+            'start' => $iStart,
+            'per_page' => $iPerPage,
+            'filter' => bx_js_string($sFilter, BX_ESCAPE_STR_APOS),
+            'filter_get' => bx_js_string($this->_aOptions['filter_get']),
+            'order_field' => bx_js_string($sOrderField, BX_ESCAPE_STR_APOS),
+            'order_dir' => bx_js_string($sOrderDir, BX_ESCAPE_STR_APOS),
+            'order_get_field' => bx_js_string($this->_aOptions['order_get_field']),
+            'order_get_dir' => bx_js_string($this->_aOptions['order_get_dir']),
+            'popup_options' => $sPopupOptions,
+            'query_append' => $sQueryAppend,
+            'confirm_messages' => $sConfirmMessages,
+            'columns' => $iColumns,
+            'bx_if:display_footer' => array(
+        		'condition' => !empty($this->_aOptions['actions_bulk']) || !empty($sPaginate),
+        		'content' => array(
+		            'bx_if:actions_bulk' => array (
+		                'condition' => !empty($this->_aOptions['actions_bulk']),
+		                'content' => array(
+		                    'actions_bulk' => $this->_getActions ('bulk'),
+		                ),
+		            ),
+		            'paginate' => $sPaginate,
+				)
+			),
+            'bx_if:display_header' => array (
+                'condition' => $isDisplayHeader,
+                'content' => array (
+                    'bx_if:actions_independent' => array (
+                        'condition' => !empty($this->_aOptions['actions_independent']),
+                        'content' => array(
+                            'actions_independent' => $this->_getActions ('independent'),
+                        ),
+                    ),
+                    'bx_if:filter' => array (
+                        'condition' => !empty($this->_aOptions['filter_fields']) || !empty($this->_aOptions['filter_fields_translatable']),
+                        'content' => array(
+                            'controls' => $this->_getFilterControls(),
+                        ),
+                    ),
+                ),
+            ),
+        );
+
+        $this->_addJsCss();
+
+        return $this->_oTemplate->parseHtmlByName('grid.html', $aVars);
+    }
+
+    /**
+     * Reset query params, like filter and page number
+     */
+    public function resetQueryParams()
+    {
+        foreach ($this->_aQueryReset as $sKey) {
+            unset($_GET[$sKey]);
+            unset($_POST[$sKey]);
+        }
+    }
+
+    protected function _getRowHead ()
+    {
+        $aRet = array();
+        foreach ($this->_aOptions['fields'] as $sKey => $a) {
+
+            $sMethod = '_getCellHeaderDefault';
+            $sCustomMethod = '_getCellHeader' . $this->_genMethodName($sKey);
+            if (method_exists($this, $sCustomMethod))
+                $sMethod = $sCustomMethod;
+
+            $aRet[] = array('header_cell' => $this->$sMethod($sKey, $a));
+        }
+        return $aRet;
+    }
+
+    protected function _getCellHeaderDefault ($sKey, $aField)
+    {
+        $sHeader = bx_process_output($aField['title']);
+
+        if (!empty($this->_aOptions['sorting_fields']) && is_array($this->_aOptions['sorting_fields']) && in_array($sKey, $this->_aOptions['sorting_fields'])) {
+            $sHeader = '<a href="javascript:void(0);" class="bx-grid-sort-handle">' . $sHeader . '</a><span class="bx-grid-sort-indi"></span>';
+            $aField['attr_head']['bx_grid_sort_head'] = $sKey;
+        }
+
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_head',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top bx-grid-header-' . $sKey, // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
+        );
+
+        return $this->_getCellHeaderWrapper ($sKey, $aField, $sHeader, $sAttr);
+    }
+
+    protected function _getCellHeaderCheckbox ($sKey, $aField)
+    {
+    	$aAttr = array(
+    		'type' => 'checkbox',
+    		'id' => $this->_sObject . '_check_all',
+    		'name' => $this->_sObject . '_check_all',
+    		'onclick' => "$('input[name=" . $this->_sObject . "_check]:not([disabled])').attr('checked', this.checked)"
+    	);
+    	if($this->_bSelectAll)
+    		$aAttr['checked'] = 'checked';
+
+    	$aField['attr'] = isset($aField['attr']) && is_array($aField['attr']) ? array_merge($aAttr, $aField['attr']) : $aAttr;
+
+        $sAttrHead = $this->_convertAttrs(
+            $aField, 'attr_head',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top bx-grid-header-' . $sKey, // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
+        );
+        return $this->_getCellHeaderWrapper ($sKey, $aField, ' <input ' . $this->_convertAttrs($aField, 'attr') . ' /> ', $sAttrHead);
+    }
+
+    protected function _getCellHeaderWrapper ($sKey, $aField, $sHeader, $sAttr)
+    {
+        return '<th ' . $sAttr . '>' . $sHeader . '</th>';
+    }
+
+    /**
+     * Check if the whole row is disabled.
+     * When row is disabled - checkbox is not selectable, actions aren't clickable and text is grayed out.
+     * By default all rows aren't disabled.
+     * @param $aRow row array
+     * @return boolean
+     */
+    protected function _isRowDisabled($aRow)
+    {
+        if (isset($aRow[$this->_aOptions['field_active']]) && !$this->_switcherState2Checked($aRow[$this->_aOptions['field_active']]))
+            return true;
+        return false;
+    }
+
+    /**
+     * Determine how actions are disabled when whole row is disabled.
+     * @param $aRow row array
+     * @return null - disable/enable actions when row is disabled/enabled, true - actions are always disabled, false - actions are always enabled
+     */
+    protected function _getActionsDisabledBehavior($aRow)
+    {
+        return null;
+    }
+
+    /**
+     * Check if the checkbox is disabled.
+     * @param $aRow row array
+     * @return boolean
+     */
+    protected function _isCheckboxDisabled($aRow)
+    {
+        return $this->_isRowDisabled($aRow);
+    }
+
+    /**
+     * Is checkbox checked by default ?
+     * By default no one checkbox is selected.
+     * @return boolean
+     */
+    protected function _isCheckboxSelected($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_bSelectAll;
+    }
+
+    /**
+     * Is switcher on by default ?
+     * By default no one switcher is on.
+     * @return boolean
+     */
+    protected function _isSwitcherOn($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_switcherState2Checked($mixedValue);
+    }
+
+    /**
+     * Convert switcher checked status to the actual value ?
+     * @return boolean
+     */
+    protected function _switcherChecked2State($isChecked)
+    {
+        return $isChecked ? 1 : 0;
+    }
+
+    /**
+     * Convert switcher value to checked(boolean) value ?
+     * @return boolean
+     */
+    protected function _switcherState2Checked($mixedState)
+    {
+        return $mixedState ? true : false;
+    }
+
+    protected function _getRowsDataDesign (&$aData)
+    {
+        $aGrid = array();
+
+        if(empty($aData))
+            $aGrid[] = array(
+                'id_row' => 0,
+                'row_class' => 'bx-grid-table-row-empty',
+                'row' => '<td class="bx-def-padding-sec-topbottom" colspan="' . count($this->_aOptions['fields']) . '">' . MsgBox(_t('_Empty')) . '</td>'
+            );
+        else
+            foreach ($aData as $aRow)
+                $aGrid[] = array(
+                    'id_row' => $this->_getRowId($aRow),
+                    'row_class' => $this->_isRowDisabled($aRow) ? 'bx-grid-table-row-disabled bx-def-font-grayed' : '',
+                    'row' => $this->_getRowDesign($aRow)
+                );
+
+        return $aGrid;
+    }
+
+    protected function _getRowId($mixedRow)
+    {
+        if(is_string($mixedRow))
+            $sId = rand(1, getrandmax());
+        else
+            $sId = $mixedRow[$this->_aOptions['field_id']];
+
+        return $this->_sObject . '_row_' . $sId;
+    }
+
+    protected function _getRowDesign($mixedRow)
+    {
+        $sRow = '';
+        if(is_string($mixedRow))
+            $sRow = '<td class="bx-def-padding-sec-topbottom bx-def-font-bold" colspan="' . count($this->_aOptions['fields']) . '">' . $mixedRow . '</td>';
+        else
+            foreach($this->_aOptions['fields'] as $sKey => $aField)
+                $sRow .= $this->_getCellDesign($sKey, $aField, $mixedRow);
+
+        return $sRow;
+    }
+
+    protected function _getCellDesign($sKey, $aField, $aRow)
+    {
+        $sMethod = '_getCellDefault';
+        if ($this->_aOptions['field_order'] == $sKey)
+            $sMethod = '_getCellOrder';
+
+        $sCustomMethod = '_getCell' . $this->_genMethodName($sKey);
+        if (method_exists($this, $sCustomMethod))
+            $sMethod = $sCustomMethod;
+
+        $mixedValue = $this->_getCellData($sKey, $aField, $aRow);
+
+        if ($aField['translatable'])
+            $mixedValue = _t($mixedValue);
+
+        $mixedValue = $this->_limitMaxLength($mixedValue, $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow);
+
+        return $this->$sMethod($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellCheckbox ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+        $sDisabled = ($this->_isCheckboxDisabled($aRow) ? 'disabled="disabled"' : '');
+        $sSelected = ($this->_isCheckboxSelected($mixedValue, $sKey, $aField, $aRow) ? 'checked="checked"' : '');
+        $sVal = $aRow[$this->_aOptions['field_id']];
+        return '<td ' . $sAttr . '><input type="checkbox" name="'. $this->_sObject . '_check" value="' . $sVal . '" ' . $sDisabled . ' ' . $sSelected . '/></td>';
+    }
+
+    protected function _getCellSwitcher ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+
+        $oForm = new BxTemplFormView(array(), $this->_oTemplate);
+        $oForm->addCssJs();
+        $aInput = array(
+            'type' => 'switcher',
+            'name' => $this->_sObject . '_switch_' . $aRow[$this->_aOptions['field_id']],
+            'caption' => '',
+            'attrs' => array (
+                'bx_grid_action_single' => 'enable',
+                'bx_grid_action_confirm' => '',
+                'bx_grid_action_data' => $aRow[$this->_aOptions['field_id']],
+            ),
+            'value' => $aRow[$this->_aOptions['field_id']],
+            'checked' => $this->_isSwitcherOn(isset($aRow[$this->_aOptions['field_active']]) ? $aRow[$this->_aOptions['field_active']] : false, $sKey, $aField, $aRow),
+        );
+        $sSwitcher = $oForm->genInput($aInput);
+        return '<td ' . $sAttr . '>' . $sSwitcher . '</td>';
+    }
+
+    protected function _getCellOrder ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+        return '<td ' . $sAttr . '><div id="' . $this->_sObject . '_cell_' . $aRow[$this->_aOptions['field_id']] . '" class="bx-grid-drag-handle"><i class="sys-icon align-justify"></i></div></td>';
+    }
+
+    protected function _getCellActions ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false  // add default styles
+        );
+
+        $mixedDisabledBehavior = $this->_getActionsDisabledBehavior($aRow);
+        $sActions = $this->_getActions('single', $aRow[$this->_aOptions['field_id']], false, null === $mixedDisabledBehavior ? $this->_isRowDisabled($aRow) : $mixedDisabledBehavior, null !== $mixedDisabledBehavior, $aRow);
+
+        return '<td ' . $sAttr . '><div class="bx-grid-cell-single-actions-wrapper bx-def-margin-thd-neg">' . $sActions . '</div></td>';
+    }
+
+    protected function _getCellDefault ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sAttr = $this->_convertAttrs(
+            $aField, 'attr_cell',
+            'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
+            isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
+        );
+        return '<td ' . $sAttr . '>' . $mixedValue . '</td>';
+    }
+
+    protected function _getActions ($sType, $sActionData = false, $isSmall = false, $isDisabled = false, $isPermanentState = false, $aRow = array())
+    {
+        $sActionsType = 'actions_' . $sType;
+        if (empty($this->_aOptions[$sActionsType]) || !is_array($this->_aOptions[$sActionsType]))
+            return '';
+        $sRet = '';
+        foreach ($this->_aOptions[$sActionsType] as $sKey => $a) {
+            $sFunc = '_getAction' . $this->_genMethodName($sKey);
+            if (!method_exists($this, $sFunc))
+                $sFunc = empty($a) ? '_getActionDivider' : '_getActionDefault';
+
+            if (!isset($a['attr']['bx_grid_action'])) {
+                $a['attr']['bx_grid_action_' . $sType] = $sKey;
+                if (!empty($sActionData))
+                    $a['attr']['bx_grid_action_data'] = $sActionData;
+                if (isset($a['confirm']))
+                    $a['attr']['bx_grid_action_confirm'] = $a['confirm'] ? 1 : 0;
+                $a['attr']['bx_grid_action_reset_paginate'] = false === strpos($sKey, 'delete') ? 0 : 1; // reset paginate after deleting row 
+            }
+
+            if ($isPermanentState && !isset($a['attr']['bx_grid_permanent_state']))
+                $a['attr']['bx_grid_permanent_state'] = 1;
+
+            $sRet .= $this->$sFunc($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+        }
+        return $sRet;
+    }
+
+    protected function _getActionDefault ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if ($a['icon_only'] && empty($a['attr']['title']) && !empty($a['title']))
+            $a['attr']['title'] = $a['title'];
+
+        $sAttr = $this->_convertAttrs(
+            $a, 'attr',
+            'bx-btn bx-def-margin-thd' . ($isSmall ? ' bx-btn-small' : '') . ($isDisabled ? ' bx-btn-disabled' : '') // add default classes
+        );
+
+        $sIcon = '';
+        $sImage = '';
+        if (!empty($a['icon'])) {
+            if (false === strpos($a['icon'], '.'))
+                $sIcon = '<i class="sys-icon ' . $a['icon'] . '"></i>';
+            elseif ($sIconUrl = $this->_oTemplate->getIconUrl($a['icon']))
+                $sImage = '<img style="background-image:url(' . $sIconUrl . ');" src="' . $this->_oTemplate->getIconUrl('spacer.gif') .'" />';
+        }
+        return '<button ' . $sAttr . '>' . $sIcon . $sImage . ($a['icon_only'] || empty($a['title']) ? '' : '<u>' . $a['title'] . '</u>') . '</button>';
+    }
+
+    protected function _getActionDivider ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        return '<div class="bx-grid-actions-divider bx-def-margin-sec-left"> | </div>';
+    }
+
+    protected function _getFilterControls ()
+    {
+        $oForm = new BxTemplStudioFormView(array());
+
+        $aInput = array(
+            'type' => 'text',
+            'name' => 'keyword',
+            'attrs' => array(
+                'id' => 'bx-grid-search-' . $this->_sObject
+            )
+        );
+
+        $this->_oTemplate->addCss('forms.css');
+        return $oForm->genRow($aInput);
+    }
+
+    protected function _limitMaxLength ($mixedValue, $sKey, $aField, $aRow, $isDisplayPopupOnTextOverflow, $bReturnString = true)
+    {
+        if ($aField['chars_limit'] > 0)
+            $mixedValue = BxTemplFunctions::getInstance()->getStringWithLimitedLength($mixedValue, $aField['chars_limit'], $isDisplayPopupOnTextOverflow, $bReturnString);
+        return $mixedValue;
+    }
+
+    protected function _convertAttrs ($aField, $sAttrName, $sClasses = false, $sStyles = false)
+    {
+        if (!empty($aField['hidden_on'])) {
+            $aHiddenOn = array(
+                pow(2, BX_DB_HIDDEN_PHONE - 1) => 'bx-def-media-phone-hide',
+                pow(2, BX_DB_HIDDEN_TABLET - 1) => 'bx-def-media-tablet-hide',
+                pow(2, BX_DB_HIDDEN_DESKTOP - 1) => 'bx-def-media-desktop-hide',
+                pow(2, BX_DB_HIDDEN_MOBILE - 1) => 'bx-def-mobile-app-hide'
+            );
+            foreach ($aHiddenOn as $iHiddenOn => $sClass)
+                if ((int)$aField['hidden_on'] & $iHiddenOn)
+                    $sClasses .= ' ' . $sClass;
+        }
+            
+        return bx_convert_array2attrs(
+            isset($aField[$sAttrName]) && is_array($aField[$sAttrName]) ? $aField[$sAttrName] : array(),
+            $sClasses,
+            $sStyles
+        );
+    }
+
+    protected function _updateOrder($mixedId, $iOrder)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sTable = $this->_aOptions['table'];
+        $sFieldId = $this->_aOptions['field_id'];
+        $sFieldOrder = $this->_aOptions['field_order'];
+        $sQuery = $oDb->prepare("UPDATE `{$sTable}` SET `{$sFieldOrder}` = ? WHERE `{$sFieldId}` = ?", $iOrder, $mixedId);
+        return $oDb->query($sQuery);
+    }
+
+    protected function _delete ($mixedId)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sTable = $this->_aOptions['table'];
+        $sFieldId = $this->_aOptions['field_id'];
+        $sQuery = $oDb->prepare("DELETE FROM `{$sTable}` WHERE `{$sFieldId}` = ?", $mixedId);
+        return $oDb->query($sQuery);
+    }
+
+    protected function _enable ($mixedId, $isChecked)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sTable = $this->_aOptions['table'];
+        $sFieldId = $this->_aOptions['field_id'];
+        $sFieldActive = $this->_aOptions['field_active'];
+        $sQuery = $oDb->prepare("UPDATE `{$sTable}` SET `$sFieldActive` = ? WHERE `{$sFieldId}` = ?", $this->_switcherChecked2State($isChecked), $mixedId);
+        return $oDb->query($sQuery);
+    }
+
+    protected function _addJsCss()
+    {
+        if ($this->_aOptions['field_order']) {
+            
+            $aUiLangs = BxBaseFormView::getJsUiLangs ();
+            $sUiLang = BxDolLanguages::getInstance()->detectLanguageFromArray ($aUiLangs);
+
+            $this->_oTemplate->addJs(array(
+                'jquery-ui/jquery-ui.custom.min.js',
+                'jquery-ui/i18n/jquery.ui.datepicker-' . $sUiLang . '.min.js',
+                'URI.js',
+            ));
+        
+            $this->_oTemplate->addCss('jquery-ui/jquery-ui.css');
+        }
+
+        $this->_oTemplate->addJs('BxDolGrid.js');
+        $this->_oTemplate->addCss('grid.css');
+
+        $this->_oTemplate->addJsTranslation('_sys_grid_confirmation');
+    }
+}
+
+/** @} */

+ 98 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseMenuAccountNotifications.php

@@ -0,0 +1,98 @@
+<?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 BxBaseMenuAccountNotifications extends BxTemplMenu
+{
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+
+        $this->addMarkers(array(
+            'studio_url' => BX_DOL_URL_STUDIO
+        ));        
+    }
+
+    protected function getMenuItemsRaw ()
+    {
+        $aItems = $this->_oQuery->getMenuItemsBy(array(
+            'type' => 'set_name', 
+            'set_name' => $this->_aObject['set_name']
+        ));
+
+        $aDuplicates = $this->_oQuery->getMenuItemsBy(array(
+            'type' => 'set_name_duplicates', 
+            'set_name' => $this->_aObject['set_name']
+        ));
+
+        $oProfile = BxDolProfile::getInstance();
+        if(!$oProfile)
+            return array();
+
+        $sModule = $oProfile->getModule();
+
+        $aResult = array();
+        foreach($aItems as $aItem) {
+            if(in_array($aItem['name'], $aDuplicates) && $aItem['module'] != $sModule)
+                continue;
+            
+            $aResult[$aItem['name']] = $aItem;
+        }
+
+        return $aResult;
+    }
+
+    /**
+     * Check if menu items is visible with extended checking for friends notifications
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        switch ($a['name']) {
+            case 'studio':
+                if (!isAdmin())
+                    return false;
+                break;
+
+            case 'cart':
+                $oPayments = BxDolPayments::getInstance();
+                if(!$oPayments->isActive())
+                    return false;
+                break;
+
+            case 'orders':
+                $oPayments = BxDolPayments::getInstance();
+                if(!$oPayments->isActive())
+                    return false;
+                break;
+        }
+
+        return true;
+    }
+
+    protected function _getTmplVarsAddon($mixedAddon, $aMenuItem)
+    {
+        return array(
+            'addon' => $mixedAddon,
+            'addonf' => $this->_oTemplate->parseHtmlByTemplateName('menu_item_addon', array(
+		        'content' => $mixedAddon
+		    ))
+			
+        );
+    }
+}
+
+/** @} */

+ 110 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseMenuFooter.php

@@ -0,0 +1,110 @@
+<?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
+ * @{
+ */
+
+/**
+ * Site main menu representation.
+ */
+class BxBaseMenuFooter extends BxTemplMenu
+{
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    public function getCode ()
+    {
+        return parent::getCode() . getVersionComment();
+    }
+    public function getMenuItems ()
+    {
+        $aItems = parent::getMenuItems();
+        foreach($aItems as $iKey => $aItem) {
+            switch($aItem['name']) {
+                case 'switch_language':
+                    $sTitle = _t('_sys_menu_item_title_switch_language_mask', $aItems[$iKey]['title'], genFlag());
+
+                    $aItems[$iKey]['title'] = $sTitle;
+                    $aItems[$iKey]['bx_if:title']['condition'] = (bool)$sTitle;
+                    $aItems[$iKey]['bx_if:title']['content']['title'] = $sTitle;
+                    break;
+
+                case 'switch_template':
+                    $aTemplates = get_templates_array(true, true);
+
+                    $sTemplateName = $this->_oTemplate->getCode();                    
+                    $sTemplateTitle = isset($aTemplates[$sTemplateName]) ? $aTemplates[$sTemplateName] : '';
+
+                    $sTitle = _t('_sys_menu_item_title_switch_template_mask', $aItems[$iKey]['title'], $sTemplateTitle);
+
+                    $aItems[$iKey]['title'] = $sTitle;
+                    $aItems[$iKey]['bx_if:title']['condition'] = (bool)$sTitle;
+                    $aItems[$iKey]['bx_if:title']['content']['title'] = $sTitle;
+                    break;
+
+                case 'copyright':
+                    $aItems[$iKey]['title_attr'] = bx_html_attribute(_t('_copyright', date('Y')));
+                    break;
+
+                case 'powered_by':
+                    $sTitleAttr = bx_html_attribute(_t('_sys_txt_powered_by'));
+
+                    $aItems[$iKey]['title_attr'] = $sTitleAttr;
+                    $aItems[$iKey]['bx_if:image']['content']['title_attr'] = $sTitleAttr;
+                    break;
+            }
+
+            $bTitleAttr = isset($aItems[$iKey]['title_attr']);
+            $bTitleAttrImage = isset($aItems[$iKey]['bx_if:image']['content']['title_attr']);
+            if(!$bTitleAttr || !$bTitleAttrImage) {
+                $sTitleAttr = bx_html_attribute(strip_tags($aItems[$iKey]['title']));
+
+                if(!$bTitleAttr)
+                    $aItems[$iKey]['title_attr'] = $sTitleAttr;
+
+                if(!$bTitleAttrImage)
+                    $aItems[$iKey]['bx_if:image']['content']['title_attr'] = $sTitleAttr;
+            }
+        }
+
+        return $aItems;
+    }
+
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        $bResult = true;
+        switch ($a['name']) {
+            case 'switch_language':
+                $aLanguages = BxDolLanguagesQuery::getInstance()->getLanguages(false, true);
+                if(count($aLanguages) <= 1)
+                    $bResult = false;
+                break;
+
+            case 'switch_template':
+                $iTemplates = 0;
+                $aTemplates = get_templates_array(true, false);
+                foreach($aTemplates as $aTemplate) {
+                    $aMixes = $this->_oQuery->getParamsMixes($aTemplate['name'], 1);
+                    if(!empty($aMixes) && is_array($aMixes))
+                        $iTemplates += count($aMixes);
+                    else 
+                        $iTemplates += 1;
+                }
+                if($iTemplates <= 1)
+                    $bResult = false;
+                break;
+        }
+
+        return $bResult;
+    }
+}
+
+/** @} */

+ 168 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseMenuSubmenu.php

@@ -0,0 +1,168 @@
+<?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 BxBaseMenuSubmenu extends BxTemplMenu
+{
+    protected $_aSocialSharingService = false;
+    protected $_sObjectActionsMenu = false;
+    protected $_sObjectSubmenu = false;
+    protected $_mixedMainMenuItemSelected = false;
+
+    protected $_sJsObject;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+
+        $this->_sJsObject = 'o' . bx_gen_method_name($this->_sObject);
+    }
+
+    /**
+     * Set current menu object in submenu
+     * @param $sMenuObject menu object name
+     * @param $sForceMainMenuSelection force main menu item selection by menu item name
+     */
+    public function setObjectSubmenu ($sMenuObject, $sForceMainMenuSelection = false)
+    {
+        $this->_sObjectSubmenu = $sMenuObject;
+        $this->_mixedMainMenuItemSelected = $sForceMainMenuSelection;
+    }
+
+    /**
+     * Set current actions menu in submenu
+     * @param $sActionsMenuObject menu object name
+     */
+    public function setObjectActionsMenu ($sActionsMenuObject)
+    {
+        $this->_sObjectActionsMenu = $sActionsMenuObject;
+    }
+
+    /**
+     * Get current actions menu in submenu
+     * @return menu object name
+     */
+    public function getObjectActionsMenu()
+    {        
+        return $this->_sObjectActionsMenu;
+    }
+
+    /**
+     * Set social sharing menu in submenu
+     * @param $a menu service call array (module, method, etc)
+     */
+    public function setServiceSocialSharing ($a)
+    {
+        $this->_aSocialSharingService = $a;
+    }
+
+    /**
+     * Get social sharing menu in submenu
+     * @return menu service call array (module, method, etc)
+     */
+    public function getServiceSocialSharing ()
+    {
+        return $this->_aSocialSharingService;
+    }
+
+    /**
+     * Get menu code.
+     * @return string
+     */
+    public function getCode ()
+    {
+        $aMenuItemSelected = $this->getSelectedMenuItem ();
+        if(isset($aMenuItemSelected['set_name']) && 'sys_site' == $aMenuItemSelected['set_name'] && 'home' == $aMenuItemSelected['name'])
+            return '';
+
+        $oMenuSubmenu = BxDolMenu::getObjectInstance($this->_sObjectSubmenu);
+        if(!$oMenuSubmenu || !$oMenuSubmenu->isVisible())
+            return '';
+
+        $sCode = $oMenuSubmenu->getCode();
+        if(empty($sCode))
+            return '';
+
+        $this->_addJsCss();
+        return $this->_oTemplate->parseHtmlByName('menu_main_submenu_bar.html', array(
+            'code' => $sCode
+        ));
+    }
+
+    public function getPageCoverParams ()
+    {
+        $aMenuItemSelected = $this->getSelectedMenuItem ();
+        if (!$aMenuItemSelected)
+            return '';
+
+        // (isset($aMenuItemSelected['set_name']) && 'sys_site' == $aMenuItemSelected['set_name'] && 'home' == $aMenuItemSelected['name']) - homepage detection
+
+        $oMenuActions = BxDolMenu::getObjectInstance($this->_sObjectActionsMenu);
+
+        $bImage = strpos($aMenuItemSelected['icon'], '.') !== false;
+        $aVars = array (
+            'object' => $this->_sObject,
+            'title' => bx_process_output($aMenuItemSelected['title']),
+            'link' => BxDolPermalinks::getInstance()->permalink($aMenuItemSelected['link']),
+            'actions' => $oMenuActions ? $oMenuActions->getCode() : '',
+            'bx_if:image' => array (
+                'condition' => $bImage,
+                'content' => array('icon_url' => $aMenuItemSelected['icon']),
+            ),
+            'bx_if:icon' => array (
+                'condition' => !$bImage && !empty($aMenuItemSelected['icon']),
+                'content' => array('icon' => $aMenuItemSelected['icon']),
+            ),
+        );
+
+        // if (!$this->_sObjectSubmenu && !$this->_mixedMainMenuItemSelected && $aMenuItemSelected['submenu_object'])
+        //    $this->_sObjectSubmenu = $aMenuItemSelected['submenu_object'];
+
+        return $aVars;
+    }
+
+    public function getSelectedMenuItem ()
+    {
+        if (is_array($this->_mixedMainMenuItemSelected))
+            return $this->_mixedMainMenuItemSelected;
+
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->_oQuery->getMenuItems();
+
+        foreach ($this->_aObject['menu_items'] as $a) {
+
+            if (isset($a['active']) && !$a['active'])
+                continue;
+
+            if (isset($a['visible_for_levels']) && !$this->_isVisible($a))
+                continue;
+
+            $isSelected = false;
+
+            if ($this->_mixedMainMenuItemSelected)
+                $isSelected = $this->_mixedMainMenuItemSelected == $a['name'];
+            else
+                $isSelected = $this->_isSelected($a) || ($this->_sObjectSubmenu && $this->_sObjectSubmenu == $a['submenu_object']);
+
+            if (!$isSelected)
+                continue;
+
+            $a = $this->_replaceMarkers($a);
+            $a['title'] = _t($a['title']);
+            return $a;
+        }
+
+        return false;
+    }
+}
+
+/** @} */

+ 530 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBasePage.php

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

+ 95 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBasePageHome.php

@@ -0,0 +1,95 @@
+<?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
+ * @{
+ */
+
+/**
+ * Homepage.
+ */
+class BxBasePageHome extends BxTemplPage
+{
+    protected $_sTourTheme = 'default';
+
+    public function __construct($aObject, $oTemplate)
+    {
+        parent::__construct($aObject, $oTemplate);
+        $this->addMarkers(array('site_title' => getParam('site_title')));
+
+        $aCover = $this->getPageCoverImage();
+
+        $bTmplVarsCover = !empty($aCover['id']);
+        $aTmplVarsCover = $bTmplVarsCover ? array('image_url' => BxDolTranscoder::getObjectInstance($aCover['transcoder'])->getFileUrlById($aCover['id'])) : array();
+
+        BxDolCover::getInstance()->set(array(
+            'class' => 'bx-cover-homepage',
+            'title' => _t('_sys_txt_homepage_cover', BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=create-account')),
+            'bx_if:bg' => array (
+                'condition' => $bTmplVarsCover,
+                'content' => $aTmplVarsCover,
+            ),
+        ), 'cover_home.html');
+        
+        $sSelName = 'home';
+        if(bx_get('i') !== false)
+            $sSelName = bx_process_input(bx_get('i'));
+
+        BxDolMenu::setSelectedGlobal('system', $sSelName);
+    }
+
+    public function getCode ()
+    {
+        $s = parent::getCode ();
+        if (isAdmin() && getParam('site_tour_home') == 'on')
+            $s .= $this->_oTemplate->parseHtmlByName('homepage_tour.html', array('tour_theme' => $this->_sTourTheme));
+        if (getParam('add_to_mobile_homepage') == 'on')
+            $s .= BxDolService::call('system', 'add_to_mobile_homepage', array(), 'TemplServices');
+        return $s;
+    }
+
+    protected function _getBlockRaw($aBlock)
+    {
+        if(strpos($aBlock['title'], 'splash') !== false) {
+            $oPermalink = BxDolPermalinks::getInstance();
+
+            $sJoinForm = $sLoginForm = '';
+            if(!isLogged()) {
+                $sJoinForm = BxDolService::call('system', 'create_account_form', array(), 'TemplServiceAccount');
+                $sLoginForm = BxDolService::call('system', 'login_form', array(), 'TemplServiceLogin');
+            }
+
+            $oTemplate = BxDolTemplate::getInstance();
+            $oTemplate->addJs(array('lottie.min.js'));
+            $aBlock['content'] = $oTemplate->parseHtmlByContent($aBlock['content'], array(
+                'join_link' => BX_DOL_URL_ROOT . $oPermalink->permalink('page.php?i=create-account'),
+                'join_form' => $sJoinForm,
+                'join_form_in_box' => !empty($sJoinForm) ? DesignBoxContent(_t('_sys_txt_splash_join'), $sJoinForm, BX_DB_PADDING_DEF) : '',
+                'login_link' => BX_DOL_URL_ROOT . $oPermalink->permalink('page.php?i=login'),
+                'login_form' => $sLoginForm,
+                'login_form_in_box' => !empty($sLoginForm) ? DesignBoxContent(_t('_sys_txt_splash_login'), $sLoginForm, BX_DB_PADDING_DEF) : ''
+            ));
+        }
+
+        return parent::_getBlockRaw($aBlock);
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+        if (isAdmin()) {
+            $this->_oTemplate->addJs(array(
+                'shepherd/js/tether.min.js',
+                'shepherd/js/shepherd.min.js',
+            ));
+            $this->_oTemplate->addCss(array(
+                'homepage.css',
+                BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'shepherd/css/|shepherd-theme-' . $this->_sTourTheme . '.css'
+            ));
+        }
+    }
+}
+
+/** @} */

+ 121 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBasePlayerHTML5.php

@@ -0,0 +1,121 @@
+<?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
+ * @{
+ */
+
+/**
+ * Basic HTML5 player representation.
+ * @see BxDolPlayer
+ */
+class BxBasePlayerHTML5 extends BxDolPlayer
+{
+    /**
+     * Standard view initialization params
+     */
+    protected static $CONF_STANDARD = "
+        <video {attrs}>
+            {webm}
+            {mp4}
+        </video>
+    ";
+
+    /**
+     * Minimal view initialization params
+     */
+    protected static $CONF_MINI = "";
+
+    /**
+     * Embed view initialization params
+     */
+    protected static $CONF_EMBED = "";
+
+    /**
+     * Available player languages
+     */
+    protected static $CONF_LANGS = array();
+
+    protected $_oTemplate;
+    protected $_bJsCssAdded = false;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+    }
+
+    public function getCodeAudio ($iViewMode, $aParams, $bDynamicMode = false)
+    {
+        // TODO:
+    }
+
+    public function getCodeVideo ($iViewMode, $aParams, $bDynamicMode = false)
+    {
+        $sInit = self::$CONF_STANDARD;
+        $sClass = 'bx-player';
+
+        // set visual mode
+        switch ($iViewMode) {
+        case BX_PLAYER_MINI:
+            $sClass .= ' bx-player-mini';
+            break;
+        case BX_PLAYER_EMBED:
+            $sClass .= ' bx-player-embed';
+            break;
+        case BX_PLAYER_STANDARD:
+        default:
+            $sClass .= ' bx-player-standard';
+        }
+
+        // attrs
+        $aAttrsDefault = array(
+            'controls' => '',
+            'controlsList' => 'nodownload',
+            'preload' => 'none',
+            'autobuffer' => '', 
+            'class' => $sClass,
+        );
+        $aAttrs = isset($aParams['attrs']) && is_array($aParams['attrs']) ? $aParams['attrs'] : array();
+        $aAttrs = array_merge($aAttrsDefault, $aAttrs);
+        if (isset($aParams['poster']) && is_string($aParams['poster']))
+            $aAttrs['poster'] = $aParams['poster'];
+        $sAttrs = bx_convert_array2attrs($aAttrs, false, isset($aParams['styles']) && is_string($aParams['styles']) ? $aParams['styles'] : false);
+
+        // generate files list for HTML5 player
+        $sFormat = getParam('sys_player_default_format');
+        $aTypes = array(
+            'webm' => '<source type="video/webm; codecs=\'vp8, vorbis\'" src="{url}" />',
+            'mp4' => '<source type="video/mp4" src="{url}" />',
+        );
+        foreach ($aTypes as $s => $ss) {
+            if (!isset($aParams[$s]))
+                continue;
+            if (is_array($aParams[$s]) && !empty($aParams[$s][$sFormat]))
+                $aParams[$s] = str_replace('{url}', $aParams[$s][$sFormat], $ss);
+            elseif (is_array($aParams[$s]) && !empty($aParams[$s]['sd']))
+                $aParams[$s] = str_replace('{url}', $aParams[$s]['sd'], $ss);
+            elseif (is_string($aParams[$s]) && !empty($aParams[$s]))
+                $aParams[$s] = str_replace('{url}', $aParams[$s], $ss);
+            else
+                $aParams[$s] = '';
+        }
+
+        // player code
+        $sCode = $this->_replaceMarkers($sInit, array(
+            'attrs' => $sAttrs,
+            'webm' => isset($aParams['webm']) ? $aParams['webm'] : '',
+            'mp4' => isset($aParams['mp4']) ? $aParams['mp4'] : '',
+        ));
+
+        return $sCode;
+    }
+}
+
+/** @} */

+ 168 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseSearchExtendedForm.php

@@ -0,0 +1,168 @@
+<?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
+ * @{
+ */
+
+/**
+ * Extended Search Form.
+ * 
+ * @see BxDolSearchExtended
+ */
+class BxBaseSearchExtendedForm extends BxTemplFormView
+{
+    protected $_iAgeMin;
+    protected $_iAgeMax;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        parent::__construct($aInfo, $oTemplate);
+
+        $this->_iAgeMin = 1;
+        $this->_iAgeMax = 75;
+    }
+
+    function getCleanValue($sName)
+    {
+        $bType = isset($this->aInputs[$sName]['type']);
+
+        //--- Process field with 'Location' type. 
+        if($bType && $this->aInputs[$sName]['type'] == 'location')
+            return array(
+                'string' => parent::getCleanValue($sName), 
+                'array' => BxDolMetatags::locationsRetrieveFromForm($sName, $this)
+            );
+        
+        if($bType && $this->aInputs[$sName]['type'] == 'location_radius'){
+            $aTmp = BxDolMetatags::locationsRetrieveFromForm($sName, $this);   
+            $sValue = bx_get($sName . '_rad');
+            $iValue = is_numeric($sValue) ? (int)$sValue : 0;
+            array_push($aTmp, $iValue);
+            return array(
+                'string' => parent::getCleanValue($sName), 
+                'array' => $aTmp
+            );
+        }
+        //--- Process field with 'Date Range Age' and 'Date-Time Range Age' type.
+        if($bType && in_array($this->aInputs[$sName]['type'], array('datepicker_range_age', 'datetime_range_age'))) {
+            $bTypeDateTime = $this->aInputs[$sName]['type'] == 'datetime_range_age';
+
+            $sMethod = $this->aFormAttrs['method'];
+            $sValue = self::getSubmittedValue($sName, $sMethod);
+
+            if(!empty($sValue) && is_string($sValue)) {
+                $aMatches = array();
+                if(!preg_match("/^([0-9]+)-([0-9]+)$/i", $sValue, $aMatches))
+                    return array();
+
+                $aArgs = array("%s-%s-%s", date('Y'), date('m'), date('d'));
+                if($bTypeDateTime) {
+                    $aArgs[0] = "%s-%s-%s %s:%s:%s";
+                    $aArgs = array_merge($aArgs, array(date('H'), date('i'), date('s')));
+                }
+
+                $aArgsFrom = $aArgs;
+                $aArgsFrom[1] -= ($aMatches[2] + 1);
+                if($bTypeDateTime)
+                    $aArgsFrom[6] += 1;
+                else
+                    $aArgsFrom[3] += 1;
+
+                $aArgsTo = $aArgs;
+                $aArgsTo[1] -= $aMatches[1];
+
+                self::setSubmittedValue($sName, array(
+                    call_user_func_array('sprintf', $aArgsFrom),
+                    call_user_func_array('sprintf', $aArgsTo)
+                ), $sMethod);
+            }
+        }
+
+        return parent::getCleanValue($sName);
+    }
+
+    public function genInput(&$aInput)
+    {
+        switch($aInput['type'])
+        {
+            case 'datepicker_range':
+            case 'datetime_range':
+                $bValue = !empty($aInput['value']) && is_array($aInput['value']);
+
+                $aInput['name'] .= '[]';
+                $aInput['type'] = str_replace('_range', '', $aInput['type']);
+
+                $aSubInputs = array($aInput, $aInput);
+                foreach($aSubInputs as $iKey => $aSubInput) {
+                    if($bValue && isset($aInput['value'][$iKey]))
+                        $aSubInput['value'] = $aInput['value'][$iKey];
+
+                    $aSubInputs[$iKey] = $this->genInputStandard($aSubInput);
+                }
+
+                return implode(' - ', $aSubInputs);
+
+            case 'datepicker_range_age':
+            case 'datetime_range_age':
+                $iMin = $this->_iAgeMin;
+                $iMax = $this->_iAgeMax;
+
+                $aInput['type'] = 'doublerange';
+				
+                if(empty($aInput['value']))
+                    $aInput['value'] = $iMin . '-' . $iMax;
+
+				if (!isset($aInput['attrs']['min']) && !isset($aInput['attrs']['max'])){
+					$aAttrs = array('min' => $iMin, 'max' => $iMax, 'step' => 1);
+					if(!empty($aInput['attrs']) && is_array($aInput['attrs']))
+						$aInput['attrs'] = array_merge($aInput['attrs'], $aAttrs);
+					else 
+						$aInput['attrs'] = $aAttrs;
+				}
+
+                return $this->genInputStandard($aInput);
+                
+            case 'location_radius':
+                return $this->genInputLocationRadius($aInput);
+        }
+
+        return parent::genInput($aInput);
+    }
+
+    public function genInputLocation(&$aInput)
+    {
+        $aInput['manual_input'] = true;
+        return parent::genInputLocation($aInput);
+    }
+
+	protected function genCustomInputAuthor($aInput)
+    {
+        $aInput['ajax_get_suggestions'] = BX_DOL_URL_ROOT . 'searchExtended.php?action=get_authors';
+
+        return $this->genCustomInputUsernamesSuggestions($aInput);
+    }
+    
+    public function genInputLocationRadius(&$aInput)
+    {
+        $aInput['manual_input'] = true;
+        
+        $aInputRadius = $aInput;
+        $aInputRadius['type'] = 'text';
+        $aInputRadius['name'] = $aInputRadius['name'] . '_rad';
+        $sValue = bx_get($aInputRadius['name']);
+        $aInputRadius['value'] = is_numeric($sValue) ? $sValue : '';
+        $aInputRadius['attrs']['placeholder'] = _t('_sys_form_input_location_radius_label');
+        
+        $aVars = array (
+            'location_input' => parent::genInputLocation($aInput),
+            'radius_input' => parent::genInputStandard($aInputRadius),
+        );
+        $this->_addCss('search_extended.css');
+        return $this->oTemplate->parseHtmlByName('form_field_location_radius.html', $aVars);
+    }
+}
+
+/** @} */

+ 338 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseServiceConnections.php

@@ -0,0 +1,338 @@
+<?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 services for connections.
+ */
+class BxBaseServiceConnections extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * get grid with friends connections
+     */
+    public function serviceConnectionsTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_connections');
+        if (!$oGrid)
+            return false;
+
+        $oGrid->setProfile($iProfileId);
+        return $oGrid->getCode();
+    }
+
+	/**
+     * get grid with friends connection requests
+     */
+    public function serviceConnectionsRequestTable ()
+    {
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_connections_requests');
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+
+    /**
+     * get number of received unconfirmed connections (friend requests)
+     * @param $sConnectionsObject connections object to get unconfirmed connections from
+     * @param $mixedId id to get connections for, if omitted then logged-in profile id is used
+     * @return number
+     */
+    public function serviceGetUnconfirmedConnectionsNum ($sConnectionsObject, $mixedId = 0)
+    {
+        $oConnection = BxDolConnection::getObjectInstance($sConnectionsObject);
+        if (!$oConnection)
+            return 0;
+
+        if (!$mixedId)
+            $mixedId = bx_get_logged_profile_id();
+
+        $i = 0;
+        $a = $oConnection->getConnectedInitiators($mixedId, 0); // get received friend requests
+        foreach ($a as $iId)
+            if (BxDolProfile::getInstance($iId))
+                ++$i;
+
+        return $i;
+    }
+
+    public function serviceGetLiveUpdatesUnconfirmedConnections($sModule, $sConnectionsObject, $aMenuItemParent, $aMenuItemChild, $iCount = 0)
+    {
+        $iProfile = bx_get_logged_profile_id();
+        $oProfile = BxDolProfile::getInstance($iProfile);
+        if(!$oProfile || $oProfile->getModule() != $sModule)
+            return false;
+
+        $iCountNew = (int)$this->serviceGetUnconfirmedConnectionsNum($sConnectionsObject, $iProfile);
+        if($iCountNew == (int)$iCount)
+			return false;
+
+        return array(
+    		'count' => $iCountNew, // required
+    		'method' => 'bx_menu_show_live_update(oData)', // required
+    		'data' => array(
+    			'code' => BxDolTemplate::getInstance()->parseHtmlByTemplateName('menu_item_addon', array(
+    				'content' => '{count}'
+                )),
+                'mi_parent' => $aMenuItemParent,
+                'mi_child' => $aMenuItemChild
+    		),  // optional, may have some additional data to be passed in JS method provided using 'method' param above.
+    	);
+    }
+
+    /**
+     * get number of initiated connections (like "my subscriptions")
+     * @param $sConnectionsObject connections object to get connections from
+     * @param $mixedId id to get connections for, if omitted then logged-in profile id is used
+     * @return number
+     */
+    public function serviceGetConnectedContentNum ($sConnectionsObject, $mixedId = 0, $isMutual = false)
+    {
+        $oConnection = BxDolConnection::getObjectInstance($sConnectionsObject);
+        if (!$oConnection)
+            return 0;
+
+        if (!$mixedId)
+            $mixedId = bx_get_logged_profile_id();
+
+        $i = 0;
+        $a = $oConnection->getConnectedContent($mixedId, $isMutual, 0, BX_CONNECTIONS_LIST_NO_LIMIT); // get received friend requests
+        foreach ($a as $iId)
+            if (BxDolProfile::getInstance($iId))
+                ++$i;
+
+        return $i;
+    }
+
+    /**
+     * get number of received connections (like "subscribed me")
+     * @param $sConnectionsObject connections object to get connections from
+     * @param $mixedId id to get connections for, if omitted then logged-in profile id is used
+     * @return number
+     */
+    public function serviceGetConnectedInitiatorsNum ($sConnectionsObject, $mixedId = 0, $isMutual = false)
+    {
+        $oConnection = BxDolConnection::getObjectInstance($sConnectionsObject);
+        if (!$oConnection)
+            return 0;
+
+        if (!$mixedId)
+            $mixedId = bx_get_logged_profile_id();
+
+        $i = 0;
+        $a = $oConnection->getConnectedInitiators($mixedId, $isMutual, 0, BX_CONNECTIONS_LIST_NO_LIMIT); // get received friend requests
+        foreach ($a as $iId)
+            if (BxDolProfile::getInstance($iId))
+                ++$i;
+
+        return $i;
+    }
+
+    /**
+     * get grid with subscriptions connections
+     */
+    public function serviceSubscriptionsTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        $CNF = &BxDolModule::getInstance($aProfile['type'])->_oConfig->CNF;
+        if(getParam($CNF['PARAM_PUBLIC_SBSN']) != 'on' && $aProfile['id'] != bx_get_logged_profile_id())
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_subscriptions');
+        if (!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'subscriptions',
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * get grid with subscribed me connections
+     */
+    public function serviceSubscribedMeTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        $CNF = &BxDolModule::getInstance($aProfile['type'])->_oConfig->CNF;
+        if(getParam($CNF['PARAM_PUBLIC_SBSD']) != 'on' && $aProfile['id'] != bx_get_logged_profile_id())
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_subscribed_me');
+        if(!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'subscribers',
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * get grid with 'relations' connections
+     */
+    public function serviceRelationsTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        if(!BxDolConnection::getObjectInstance('sys_profiles_relations')->isRelationAvailableFromProfile($aProfile['type']))
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_relations');
+        if (!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'relations',
+            'content' => $sContent
+        ));
+    }
+
+    /**
+     * get grid with 'related me' connections
+     */
+    public function serviceRelatedMeTable ($iProfileId = 0)
+    {
+        if(!$iProfileId && bx_get('profile_id') !== false)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        $aProfile = BxDolProfile::getInstance($iProfileId)->getInfo();
+        if(empty($aProfile) || !is_array($aProfile))
+            return false;
+
+        if(!BxDolConnection::getObjectInstance('sys_profiles_relations')->isRelationAvailableWithProfile($aProfile['type']))
+            return false;
+
+        $oGrid = BxDolGrid::getObjectInstance('sys_grid_related_me');
+        if(!$oGrid)
+            return false;
+
+        $oGrid->setProfileId($iProfileId);
+        $sContent = $oGrid->getCode();
+        if(empty($sContent))
+            return false;
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('connections_list.html', array(
+            'name' => 'related-me',
+            'content' => $sContent
+        ));
+    }
+
+    /*
+     * Get notification data for Notifications module - action Subscribe. 
+     */
+	public function serviceGetNotificationsPost($aEvent)
+    {
+    	$iProfile = (int)$aEvent['object_id'];
+    	$oProfile = BxDolProfile::getInstance($iProfile);
+        if(!$oProfile)
+			return array();
+
+		return array(
+			'entry_sample' => '_sys_profile_sample_single',
+			'entry_url' => $oProfile->getUrl(),
+			'entry_caption' => $oProfile->getDisplayName(),
+			'entry_author' => $oProfile->id(),
+			'lang_key' => '_sys_profile_subscription_added',
+		);
+    }
+
+    /*
+     * Get notification data for Notifications module - action Friend. 
+     */
+    public function serviceGetNotificationsPostFriendship($aEvent)
+    {
+    	$iProfile = (int)$aEvent['object_id'];
+    	$oProfile = BxDolProfile::getInstance($iProfile);
+        if(!$oProfile)
+            return array();
+
+        $sLangKey = '_sys_profile_friendship_added';
+        if(isset($aEvent['content']['request']) && (int)$aEvent['content']['request'] == 1)
+            $sLangKey = '_sys_profile_friend_request_added';
+
+        return array(
+            'entry_sample' => '_sys_profile_sample_single',
+            'entry_url' => $oProfile->getUrl(),
+            'entry_caption' => $oProfile->getDisplayName(),
+            'entry_author' => $oProfile->id(),
+            'lang_key' => $sLangKey,
+        );
+    }
+
+    public function serviceAlertResponseConnections($oAlert)
+    {
+        $sMethod = 'process' . bx_gen_method_name($oAlert->sUnit . '_' . $oAlert->sAction);
+        if(!method_exists($this, $sMethod))
+            return;
+            
+        $this->$sMethod($oAlert);
+    }
+
+    protected function processSysProfilesFriendsConnectionAdded(&$oAlert)
+    {
+        if((int)$oAlert->aExtras['mutual'] != 1)
+            return;
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        $oConnection->addConnection((int)$oAlert->aExtras['initiator'], (int)$oAlert->aExtras['content'], array('alert_extras' => array('silent_mode' => 1)));
+        $oConnection->addConnection((int)$oAlert->aExtras['content'], (int)$oAlert->aExtras['initiator'], array('alert_extras' => array('silent_mode' => 1)));
+    }
+
+    protected function processSysProfilesFriendsConnectionRemoved(&$oAlert)
+    {
+        if((int)$oAlert->aExtras['mutual'] != 1)
+            return;
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        $oConnection->removeConnection((int)$oAlert->aExtras['initiator'], (int)$oAlert->aExtras['content']);
+        $oConnection->removeConnection((int)$oAlert->aExtras['content'], (int)$oAlert->aExtras['initiator']);
+    }
+}
+
+/** @} */

+ 306 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/scripts/BxBaseServiceProfiles.php

@@ -0,0 +1,306 @@
+<?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 profiles handling functionality.
+ */
+class BxBaseServiceProfiles extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceGetProfileTypes()
+    {
+        $aTypes = array(
+			'' => _t('_Select_one')
+        );
+
+        $aModules = $this->_getProfilesModules();
+        foreach($aModules as $aModule) {
+			if(empty($aModule) || !is_array($aModule))
+				continue;
+
+            $sTitleKey = '_' . $aModule['name'];
+            $sTitle = _t($sTitleKey);
+
+            $aTypes[$aModule['name']] = !empty($sTitle) && $sTitle != $sTitleKey ? $sTitle : $aModule['title'];
+		}
+
+        return $aTypes;
+    }
+
+    public function serviceProfileStats ($iProfileId = 0)
+    {
+        if (!$iProfileId && !($iProfileId = bx_get_logged_profile_id()))
+            return '';
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return '';
+
+        $oAcl = BxDolAcl::getInstance();
+        $aAcl = $oAcl->getMemberMembershipInfo($iProfileId);
+        $aAclInfo = $oAcl->getMembershipInfo($aAcl['id']);
+
+        $aVars = array(
+            'profile_id' => $oProfile->id(),
+            'profile_url' => $oProfile->getUrl(),
+            'profile_edit_url' => $oProfile->getEditUrl(),
+            'profile_title' => $oProfile->getDisplayName(),
+            'profile_title_attr' => bx_html_attribute($oProfile->getDisplayName()),
+            'profile_ava_url' => $oProfile->getAvatar(),
+            'profile_unit' => $oProfile->getUnit(0, array('template' => array(
+                'name' => 'unit_wo_info',
+                'size' => 'ava'
+            ))),
+            'profile_acl_title' => _t($aAclInfo['name']),
+            'profile_acl_icon' => $aAclInfo['icon'],
+            'menu' => BxDolMenu::getObjectInstance('sys_profile_stats')->getCode(),
+        );
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('profile_stats.html', $aVars);
+    }
+
+    public function serviceGetMenuAddonProfileEdit($iProfileId = 0, $sCaption = '')
+    {
+    	if(empty($sCaption))
+    		$sCaption = _t('_Edit');
+
+		$oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+        	return '';
+
+		$sModule = $oProfile->getModule();
+		$sMethod = 'profile_edit_url';
+		if(!BxDolRequest::serviceExists($sModule, $sMethod))
+			return '';
+
+		return BxDolTemplate::getInstance()->parseLink(BxDolService::call($sModule, $sMethod, array($oProfile->getContentId())), $sCaption);
+    }
+
+    public function serviceProfileMembership ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+		return BxDolAcl::getInstance()->getProfileMembership($iProfileId);
+    }
+
+    public function serviceProfileNotifications ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        $oMenu = BxDolMenu::getObjectInstance('sys_account_notifications');
+        if(!$oMenu)
+            return 0;
+
+        $iNum = 0;
+        $aMenuItems = $oMenu->getMenuItems ();
+        foreach ($aMenuItems as $r) {
+            if (isset($r['bx_if:addon']) && $r['bx_if:addon']['condition'])
+                $iNum += $r['bx_if:addon']['content']['addon'];
+        }
+
+        return $iNum;
+    }
+
+    public function serviceGetCountOnlineProfiles ()
+    {
+        $oProfilesQuery = BxDolProfileQuery::getInstance();
+        return $oProfilesQuery->getOnlineCount();
+    }
+    
+    public function serviceGetProfilesModules ()
+    {
+        if (getParam('sys_db_cache_enable')) { // get list of profiles  modules from db cache, cache is invalidated when new module is installed
+
+            $oDb = BxDolDb::getInstance();
+
+            $oCache = $oDb->getDbCacheObject ();
+
+            $sKey = $oDb->genDbCacheKey('profiles_modules_array');
+            $sKeyTs = $oDb->genDbCacheKey('profiles_modules_ts');
+
+            $mixedRetTs = $oCache->getData($sKeyTs);
+            $mixedRet = $oCache->getData($sKey);
+
+            $iNewestModuleTs = $this->_getLatestModuleTimestamp ();
+
+            if ($mixedRetTs != null && $mixedRet !== null && $mixedRetTs == $iNewestModuleTs) {
+
+                $aModulesArray = $mixedRet;
+
+            } else {
+
+                $aModulesArray = $this->_getProfilesModules ();
+
+                $oCache->setData($sKey, $aModulesArray);
+                $oCache->setData($sKeyTs, $iNewestModuleTs);
+            }
+
+        } else {
+
+            $aModulesArray = $this->_getProfilesModules ();
+
+        }
+
+        return $aModulesArray;
+    }
+
+    public function serviceProfilesFriends ($iLimit = 20)
+    {
+        if (!($iProfileId = bx_get_logged_profile_id()))
+            return array();
+        
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        if (!$oConnection)
+            return array();
+
+        if (!($a = $oConnection->getConnectedContent ($iProfileId, true, 0, $iLimit)))
+            return array();
+
+        $aRet = array();
+        foreach ($a as $iId) {
+            $oProfile = BxDolProfile::getInstance($iId);
+
+            $aRet[] = array (
+            	'label' => $oProfile->getDisplayName(), 
+                'value' => $iId, 
+                'url' => $oProfile->getUrl(),
+            	'thumb' => $oProfile->getThumb(),
+                'unit' => $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+            );
+        }
+        return $aRet;
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit = 20)
+    {
+        // display friends by default
+        if (!$sTerm)
+            return $this->serviceProfilesFriends($iLimit);
+
+        // get list of "profiles" modules
+        $aModules = $this->serviceGetProfilesModules();
+
+        // search in each module
+        $a = array();
+        foreach ($aModules as $aModule) {
+            if (!BxDolService::call($aModule['name'], 'act_as_profile'))
+                continue;
+            $a = array_merge($a, BxDolService::call($aModule['name'], 'profiles_search', array($sTerm, 10)));
+        }
+
+        // sort result
+        usort($a, function($r1, $r2) {
+            return strcmp($r1['label'], $r2['label']);
+        });
+
+        // return as array
+        return array_slice($a, 0, $iLimit);
+    }
+
+    public function serviceProfilesList ($iAccountId = 0)
+    {
+        $oProfilesQuery = BxDolProfileQuery::getInstance();
+
+        $aProfiles = $oProfilesQuery->getProfilesByAccount($iAccountId ? $iAccountId : getLoggedId());
+        if (!$aProfiles)
+            return false;
+
+        $s = '';
+        foreach ($aProfiles as $aProfile)
+            if ($aProfile['type'] != 'system')
+                $s .= BxDolService::call($aProfile['type'], 'profile_unit', array($aProfile['content_id']));
+
+        if (!$s)
+            $s = MsgBox(_t('_sys_txt_empty'));
+
+        return $s . '<div class="bx-clear"></div>';
+    }
+
+    public function serviceAccountProfileSwitcher ($iAccountId = false, $iActiveProfileId = null, $sUrlProfileAction = '', $bShowAll = 0, $sButtonTitle = '', $sProfileTemplate = '')
+    {
+    	$oTemplate = BxDolTemplate::getInstance();
+    	BxDolInformer::getInstance($oTemplate)->setEnabled(false);
+
+        $oProfilesQuery = BxDolProfileQuery::getInstance();
+
+        $aProfiles = $oProfilesQuery->getProfilesByAccount($iAccountId ? $iAccountId : getLoggedId());
+        if (!$aProfiles)
+            return false;
+
+        if (null === $iActiveProfileId)
+            $iActiveProfileId = bx_get_logged_profile_id();
+
+		$oModuleDb = BxDolModuleQuery::getInstance();
+
+        $aVars = array (
+            'bx_repeat:row' => array(),
+        );
+        foreach ($aProfiles as $aProfile) {
+            if (!$bShowAll && $iActiveProfileId == $aProfile['id'])
+                continue;
+
+        	if(!$oModuleDb->isEnabledByName($aProfile['type']))
+        		continue;
+
+            if (!BxDolService::call($aProfile['type'], 'act_as_profile'))
+                continue;
+            $aVars['bx_repeat:row'][] = array (
+                'class' => $iActiveProfileId == $aProfile['id'] ? '' : 'bx-def-color-bg-box',
+                'bx_if:active' => array (
+                    'condition' => $iActiveProfileId == $aProfile['id'],
+                    'content' => array('id' => $aProfile['id']),
+                ),
+                'bx_if:inactive' => array (
+                    'condition' => $iActiveProfileId != $aProfile['id'],
+                    'content' => array(
+                        'id' => $aProfile['id'], 
+                        'button_title' => $sButtonTitle ? $sButtonTitle : _t('_sys_txt_switch_profile_context'),
+                        'url_switch' => $sUrlProfileAction ? str_replace('{profile_id}', $aProfile['id'], $sUrlProfileAction) : BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher', array('switch_to_profile' => $aProfile['id'], 'redirect_back' => 1))
+                    ),
+                ),
+                'unit' => BxDolService::call($aProfile['type'], 'profile_unit', array($aProfile['content_id'], array('template' => $sProfileTemplate))),
+            );
+        }
+
+        $oTemplate->addCss('account.css');
+        return array(
+            'content' => $oTemplate->parseHtmlByName('profile_switch_row.html', $aVars),
+        );
+    }
+
+    protected function _getLatestModuleTimestamp ()
+    {
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1, 'order_by' => '`date` ASC'));
+        if (empty($aModules))
+            return 0;
+        $aModuleNewest = array_pop($aModules);
+        return $aModuleNewest['date'];
+    }
+
+    protected function _getProfilesModules ()
+    {
+        $aRet = array();
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'modules', 'active' => 1));
+        foreach ($aModules as $aModule) {
+            $oModule = BxDolModule::getInstance($aModule['name']);
+            if ($oModule instanceof iBxDolProfileService && $oModule->serviceActAsProfile())
+                $aRet[] = $aModule;
+        }
+        return $aRet;
+    }
+
+}
+
+/** @} */

+ 6 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/unit_meta_item.html

@@ -0,0 +1,6 @@
+<bx_if:span><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="__style_prefix__-item"><span class="bx-def-font-grayed __class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</span></span></bx_if:span>
+<bx_if:a><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="__style_prefix__-item"><a class="__class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a></span></bx_if:a>
+<bx_if:button><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="__style_prefix__-item"><a class="bx-btn __class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a></span></bx_if:button>
+<bx_if:sbutton><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="__style_prefix__-item"><a class="bx-btn bx-btn-small __class__" <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</a></span></bx_if:sbutton>
+<bx_if:custom><span class="__style_prefix__-div bx-def-font-grayed">&nbsp;&#183;&nbsp;</span><span class="__style_prefix__-item">__content__</span></bx_if:custom>
+<bx_if:nl></bx_if:nl>

+ 16 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/vote_do_vote_stars.html

@@ -0,0 +1,16 @@
+<div class="__style_prefix__-element">
+    <div class="__style_prefix__-underlay">
+        <bx_repeat:stars><i class="__style_prefix__-star sys-icon far star"></i></bx_repeat:stars>
+    </div>
+    <bx_if:show_legend>
+        <div class="__style_prefix__-legend bx-def-font-small">
+            <bx_repeat:legend><span class="bx-vote-star">__value__</span></bx_repeat:legend>
+        </div>
+    </bx_if:show_legend>
+    <div class="__style_prefix__-slider">
+        <bx_repeat:slider><i class="__style_prefix__-star sys-icon star"></i></bx_repeat:slider>
+    </div>
+    <div class="__style_prefix__-buttons">
+        <bx_repeat:buttons><a class="__style_prefix__-button" href="javascript:void(0);" onmouseover="__js_object__.over(this);" onmouseout="__js_object__.out(this);" onclick="__js_object__.vote(this, __value__)"><i class="__style_prefix__-star sys-icon star"></i></a></bx_repeat:buttons>
+    </div>
+</div>

+ 15 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files/template/vote_legend.html

@@ -0,0 +1,15 @@
+<div id="__html_id__" class="__style_prefix__-legend __style_prefix__-legend-__type__ bx-def-margin-top-auto">
+    <bx_repeat:items>
+        <div class="__style_prefix__-legend-item bx-def-margin-thd-topbottom bx-clearfix" bx_vote_item_value="__value__">
+            <div class="bx-vote-element-holder">
+                <div class="__style_prefix__-underlay">
+                    <bx_repeat:stars><i class="__style_prefix__-star sys-icon far star"></i></bx_repeat:stars>
+                </div>
+                <div class="__style_prefix__-slider">
+                    <bx_repeat:slider><i class="__style_prefix__-star sys-icon star"></i></bx_repeat:slider>
+                </div>
+            </div>
+            <div class="bx-vote-counter-holder bx-def-margin-sec-left bx-def-font-grayed">__label__</div>
+        </div>
+    </bx_repeat:items>
+</div>

+ 6 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/files_delete.php

@@ -0,0 +1,6 @@
+<?php
+
+$aFilesDelete = array(
+    'inc/js/classes/BxDolMenuSubmenu.js',
+);
+

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

@@ -0,0 +1,18 @@
+<?php
+
+    if (!$this->oDb->isFieldExists('sys_objects_grid', 'responsive'))
+        $this->oDb->query("ALTER TABLE `sys_objects_grid` ADD `responsive` tinyint(4) NOT NULL DEFAULT '1' AFTER `visible_for_levels`");
+
+    if ($this->oDb->isFieldExists('sys_objects_grid', 'responsive'))
+        $this->oDb->query("UPDATE `sys_objects_grid` SET `responsive` = 0 WHERE `object` IN('sys_grid_connections', 'sys_grid_connections_requests', 'sys_grid_subscriptions', 'sys_grid_subscribed_me', 'sys_grid_relations', 'sys_grid_related_me')");
+
+
+    if (!$this->oDb->isFieldExists('sys_grid_fields', 'hidden_on'))
+        $this->oDb->query("ALTER TABLE `sys_grid_fields` ADD `hidden_on` varchar(255) NOT NULL DEFAULT '' AFTER `params`");
+
+    if ($this->oDb->isFieldExists('sys_grid_fields', 'hidden_on')) {
+        $this->oDb->query("UPDATE `sys_grid_fields` SET `hidden_on` = '1' WHERE `object` = 'sys_grid_connections' AND `name` = 'info'");
+        $this->oDb->query("UPDATE `sys_grid_fields` SET `hidden_on` = '1' WHERE `object` = 'sys_grid_connections_requests' AND `name` = 'info'");
+    }
+
+    return true;

Разница между файлами не показана из-за своего большого размера
+ 7 - 0
upgrade/files/10.0.0.B1-10.0.0.B2/sql.sql


Некоторые файлы не были показаны из-за большого количества измененных файлов