Pārlūkot izejas kodu

Merge branch 'master' of https://github.com/boonex/dolphin

AntonLV 12 gadi atpakaļ
vecāks
revīzija
9f432d64c0
100 mainītis faili ar 16723 papildinājumiem un 20 dzēšanām
  1. 5 1
      inc/classes/BxDolProfileQuery.php
  2. 5 1
      inc/js/functions.js
  3. 1 0
      modules/boonex/english/data/langs/system.xml
  4. 6 0
      modules/boonex/ocean/data/template/system/css/menu.css
  5. 9 5
      template/css/account.css
  6. 4 0
      template/css/default.less
  7. 17 6
      template/css/menu.css
  8. 4 3
      template/menu_account_popup.html
  9. 1 3
      template/profile_switch_row.html
  10. 5 0
      template/scripts/BxBaseMenuAccountPopup.php
  11. 1 0
      template/scripts/BxBaseServiceProfiles.php
  12. 1 1
      template/search.html
  13. 8 0
      upgrade/files/8.0.0.A7-8.0.0.A8/check.php
  14. 32 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/grid.php
  15. 979 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolCmts.php
  16. 313 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolCmtsQuery.php
  17. 221 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolLiveUpdates.php
  18. 38 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolLiveUpdatesQuery.php
  19. 280 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolMenu.php
  20. 364 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolMetatags.php
  21. 85 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolObject.php
  22. 423 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolProfile.php
  23. 859 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolSearch.php
  24. 2226 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolTemplate.php
  25. 414 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolUploader.php
  26. 501 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/classes/BxDolCmts.js
  27. 61 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/classes/BxDolLiveUpdates.js
  28. 315 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/classes/BxDolUploader.js
  29. 619 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/functions.js
  30. 516 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/jquery.dolPopup.js
  31. 12 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/version.inc.php
  32. 19 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/live_updates.php
  33. 57 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralConfig.php
  34. 223 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php
  35. 143 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralGridAdministration.php
  36. 38 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralMenuManageTools.php
  37. 307 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralModule.php
  38. 46 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralTemplate.php
  39. 15 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/template/css/manage_tools.css
  40. 148 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/notifications/classes/BxBaseModNotificationsConfig.php
  41. 62 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/notifications/classes/BxBaseModNotificationsTemplate.php
  42. 111 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php
  43. 168 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileGridAdministration.php
  44. 31 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileGridCommon.php
  45. 29 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileGridModeration.php
  46. 25 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileMenuManageTools.php
  47. 375 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileModule.php
  48. 131 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextGridAdministration.php
  49. 31 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextGridCommon.php
  50. 29 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextGridModeration.php
  51. 25 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextMenuManageTools.php
  52. 365 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextModule.php
  53. 207 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextTemplate.php
  54. 3 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/template/entry-location.html
  55. 6 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/template/entry-text.html
  56. 16 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/template/unit.html
  57. 799 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/classes/BxDolStudioInstaller.php
  58. 229 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/classes/BxDolStudioUpdater.php
  59. 60 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/template/_header.html
  60. 251 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/template/css/launcher.css
  61. 51 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/template/css/menu_top.css
  62. 77 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/_header.html
  63. 67 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/_sub_header.html
  64. 1 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/bx_div.html
  65. 15 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/comment_actions.html
  66. 7 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/comments_notification.html
  67. 82 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/_mixins.less
  68. 219 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/cmts.css
  69. 464 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/common.css
  70. 527 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/default.less
  71. 383 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/forms.css
  72. 127 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/general.css
  73. 97 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/media-phone.css
  74. 446 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/menu.css
  75. 73 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/uploaders.css
  76. 16 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_account_popup.html
  77. 19 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_floating_blocks.html
  78. 19 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_floating_blocks_big.html
  79. 5 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_main_submenu_wrapper.html
  80. 8 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_toolbar.html
  81. 12 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/popup_box.html
  82. 9 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/popup_trans.html
  83. 741 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseCmts.php
  84. 49 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseCmtsServices.php
  85. 96 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseConfig.php
  86. 41 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseLiveUpdates.php
  87. 27 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseLiveUpdatesServices.php
  88. 178 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenu.php
  89. 108 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuSetAclLevel.php
  90. 29 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuSite.php
  91. 111 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuSubmenu.php
  92. 60 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuToolbar.php
  93. 23 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBasePageDashboard.php
  94. 98 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseUploaderHTML5.php
  95. 62 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseUploaderSimple.php
  96. 10 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/search.html
  97. 43 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/uploader_form_html5.html
  98. 39 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files/template/uploader_form_simple.html
  99. 8 0
      upgrade/files/8.0.0.A7-8.0.0.A8/files_delete.php
  100. 2 0
      upgrade/files/8.0.0.A7-8.0.0.A8/script.php

+ 5 - 1
inc/classes/BxDolProfileQuery.php

@@ -49,7 +49,11 @@ class BxDolProfileQuery extends BxDolDb implements iBxDolSingleton
      */
     public function getProfilesByAccount ($iAccountId)
     {
-        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `account_id` = ?", $iAccountId);
+        bx_import('BxDolAccountQuery');
+        $oAccountQuery = BxDolAccountQuery::getInstance();
+        $aAccountInfo = $oAccountQuery->getInfoById($iAccountId);
+
+        $sSql = $this->prepare("SELECT * FROM `sys_profiles` WHERE `account_id` = ? ORDER BY `id` = ? DESC", $iAccountId, $aAccountInfo['profile_id']);
         return $this->getAllWithKey($sSql, 'id');
     }
 

+ 5 - 1
inc/js/functions.js

@@ -377,8 +377,12 @@ function bx_menu_slide (jSel, e, sPosition) {
         });
 
         setTimeout(function () {
-           
+
+            var iWidthPrev = $(window).width();
             $(window).on('resize.bx-sliding-menu', function () {
+                if ($(this).width() == iWidthPrev)
+                    return;
+                iWidthPrev = $(this).width();
                 fCloseAllOpened();
             });
  

+ 1 - 0
modules/boonex/english/data/langs/system.xml

@@ -2023,6 +2023,7 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_txt_login_description"><![CDATA[Not a member yet? <a href="{0}">Join now!</a>]]></string>
 	<string name="_sys_txt_login_error"><![CDATA[Entered email or password is incorrect. Please try again.]]></string>
 	<string name="_sys_txt_login_invalid_role"><![CDATA[Access denied]]></string>
+    <string name="_sys_txt_my_profiles"><![CDATA[<b class="bx-def-font-h1">@</b> my profiles...</a>]]></string>
     <string name="_sys_txt_new_picture"><![CDATA[New Picture]]></string>
     <string name="_sys_txt_no_picture_uploaded"><![CDATA[No picture uploaded]]></string>
     <string name="_sys_txt_n_friends"><![CDATA[{0} friends]]></string>

+ 6 - 0
modules/boonex/ocean/data/template/system/css/menu.css

@@ -32,6 +32,12 @@ ul.bx-menu-toolbar li a .sys-icon-a {
     margin-top:1rem;
 }
 
+/* --------------- floating blocks menu */
+
+.bx-sliding-menu-fullheight {
+    height:calc(100% - 4rem);
+}
+
 /* --------------- account popup menu */
 .bx-menu-account-popup-profile {
 	font-weight: bold;

+ 9 - 5
template/css/account.css

@@ -1,17 +1,21 @@
 
+.sys-profile-switch-row {
+    margin-top:1px;
+}
+
 .sys-profile-switch-row-control,
 .sys-profile-switch-row-unit {
     display:inline-block;
     vertical-align:middle;
 }
 
-.sys-profile-switch-row-unit .bx-persons-unit {
-    margin-bottom:0;
+.sys-profile-switch-row-unit .bx-def-unit {
+    margin:0;
 }
 
 .sys-profile-switch-row-control .sys-icon {
     display:inline-block;
-    width:32px;
-    height:32px;
-    font-size:32px;
+    width:2rem;
+    height:2rem;
+    font-size:2rem;
 }

+ 4 - 0
template/css/default.less

@@ -19,6 +19,10 @@
 .bx-def-margin-neg { 
     .margin(-@bx-margin);
 }
+.bx-def-margin-neg-leftright { 
+    .margin-left(-@bx-margin);
+    .margin-right(-@bx-margin);
+}
 .bx-def-margin-rightbottomleft {
     .margin-right(@bx-margin);
     .margin-bottom(@bx-margin);

+ 17 - 6
template/css/menu.css

@@ -276,17 +276,33 @@ ul.bx-menu-toolbar li .sys-icon {
 }
 
 .bx-sliding-menu-main {
-    width:100%;
+    width:100%;    
     opacity: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-main ul.bx-menu-floating-blocks-big {
     display:table;
     margin:0 auto;
     white-space:nowrap;
 }
 
+.bx-sliding-menu-main .bx-menu-account-popup-profile-switcher {
+    overflow:hidden;
+
+}
+
+.bx-menu-account-popup-profile-switcher-link b {
+    vertical-align:middle;
+}
+
 .bx-popup-content .bx-menu-floating-blocks {
     width:256px;
     width:16rem;
@@ -439,8 +455,3 @@ ul.bx-menu-btns-hor li {
 .bx-popup-content .bx-menu-account-popup {
     min-width:17em;
 }
-
-.bx-menu-account-popup-switch-profile:hover,
-.bx-menu-account-popup-switch-profile {
-    text-decoration:none;
-}

+ 4 - 3
template/menu_account_popup.html

@@ -1,16 +1,17 @@
 
-<div id="bx-sliding-menu-account" class="bx-sliding-menu-main bx-def-z-index-nav bx-def-color-bg-block bx-def-border-bottom bx-search-site" style="display:none; overflow:hidden; height:100%; opacity:1;">
+<div id="bx-sliding-menu-account" class="bx-sliding-menu-main bx-sliding-menu-fullheight bx-def-z-index-nav bx-def-color-bg-block bx-def-border-bottom bx-def-box-sizing bx-search-site" style="display:none; opacity:1;">
 
     <a class="bx-sliding-menu-close bx-def-margin bx-def-font-h1" href="javascript:void(0);" onclick="bx_menu_slide('#bx-sliding-menu-account')"><i class="sys-icon times"></i></a>
 
     <div class="bx-menu-account-popup bx-menu-object-__object__ bx-def-padding">
         <div class="bx-def-font-h2">
             <span class="bx-menu-account-popup-profile">__profile_display_name__</span>
-            <a title="<bx_text_attribute:_sys_switch_profile />" class="bx-menu-account-popup-switch-profile bx-def-margin-sec-left" href="__url_switch_profile__"><i class="sys-icon exchange"></i></a>
         </div>
         <div class="bx-def-margin-top bx-def-margin-bottom">__menu_account__</div>
         <div class="bx-def-color-bg-sec" style="height:0.5em; margin-left:-3em; margin-right:-3em;">1</div>
-        <div>__menu_notifications__</div>
+        <div>__menu_notifications__</div>        
     </div>
+    <div class="bx-menu-account-popup-profile-switcher bx-def-box-sizing bx-def-padding-leftright">__profile_switcher__</div>
+    <div class="bx-menu-account-popup-profile-switcher-link bx-def-padding"><a href="__url_switch_profile__"><bx_text:_sys_txt_my_profiles /></div>
 </div>
 

+ 1 - 3
template/profile_switch_row.html

@@ -1,8 +1,6 @@
 <bx_repeat:row>
 
-<hr class="bx-def-hr-auto" />
-
-<div class="sys-profile-switch-row">
+<div class="sys-profile-switch-row bx-def-margin-neg-leftright bx-def-padding-leftright bx-def-padding-sec-topbottom __class__">
     <div class="sys-profile-switch-row-control bx-def-margin-right">
         <bx_if:active>
             <i class="sys-icon check-square-o"></i>

+ 5 - 0
template/scripts/BxBaseMenuAccountPopup.php

@@ -24,11 +24,16 @@ class BxBaseMenuAccountPopup extends BxTemplMenu
     protected function _getTemplateVars ()
     {
         $aVars = parent::_getTemplateVars ();
+
         $aVars['bx_repeat:menu_items'] = array(true);
         $aVars['profile_display_name'] = BxDolProfile::getInstance()->getDisplayName();
         $aVars['url_switch_profile'] = BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher');
         $aVars['menu_account'] = BxDolMenu::getObjectInstance('sys_account')->getCode();
         $aVars['menu_notifications'] = BxDolMenu::getObjectInstance('sys_account_notifications')->getCode();
+
+        $a = BxDolService::call('system', 'account_profile_switcher', array(), 'TemplServiceProfiles');
+        $aVars['profile_switcher'] = $a['content'];
+
         return $aVars;
     }
 }

+ 1 - 0
template/scripts/BxBaseServiceProfiles.php

@@ -154,6 +154,7 @@ class BxBaseServiceProfiles extends BxDol
             if ($aProfile['type'] == 'system') // skip system account profile
                 continue;
             $aVars['bx_repeat:row'][] = array (
+                'class' => $iLoggedPofileId == $aProfile['id'] ? '' : 'bx-def-color-bg-box',
                 'bx_if:active' => array (
                     'condition' => $iLoggedPofileId == $aProfile['id'],
                     'content' => array('id' => $aProfile['id']),

+ 1 - 1
template/search.html

@@ -1,5 +1,5 @@
 
-<div id="bx-sliding-menu-search" class="bx-sliding-menu-main bx-def-z-index-nav bx-def-color-bg-block bx-search-site" style="display:none; height:100%; opacity:1;">
+<div id="bx-sliding-menu-search" class="bx-sliding-menu-main bx-sliding-menu-fullheight bx-def-z-index-nav bx-def-color-bg-block bx-search-site" style="display:none; opacity:1;">
     <a class="bx-sliding-menu-close bx-def-margin bx-def-font-h1" href="javascript:void(0);" onclick="bx_menu_slide('#bx-sliding-menu-search')"><i class="sys-icon times"></i></a>
     <div class="bx-def-padding">
         <div class="bx-def-font-h1 bx-def-margin-bottom">&nbsp;</div>

+ 8 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/check.php

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

+ 32 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/grid.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+require_once(BX_DIRECTORY_PATH_INC . "utils.inc.php");
+
+bx_import('BxDolLanguages');
+
+$sObject = bx_process_input(bx_get('o'));
+if (!$sObject)
+    exit;
+
+bx_import('BxDolGrid');
+$oGrid = BxDolGrid::getObjectInstance($sObject);
+if (!$oGrid) {
+    // no such grid object available
+    exit;
+}
+
+$sAction = 'performAction' . bx_gen_method_name(bx_process_input(bx_get('a')));
+if (method_exists($oGrid, $sAction)) {
+    $oGrid->$sAction();
+}
+
+/** @} */

+ 979 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolCmts.php

@@ -0,0 +1,979 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolPermalinks');
+bx_import('BxDolCmtsQuery');
+
+define('BX_CMT_OLD_VOTES', 365*86400); ///< comment votes older than this number of seconds will be deleted automatically
+
+define('BX_CMT_ACTION_POST', 'post');
+define('BX_CMT_ACTION_EDIT', 'edit');
+
+define('BX_CMT_DISPLAY_FLAT', 'flat');
+define('BX_CMT_DISPLAY_THREADED', 'threaded');
+
+define('BX_CMT_BROWSE_HEAD', 'head');
+define('BX_CMT_BROWSE_TAIL', 'tail');
+define('BX_CMT_BROWSE_POPULAR', 'popular');
+define('BX_CMT_BROWSE_CONNECTION', 'connection');
+
+define('BX_CMT_ORDER_BY_DATE', 'date');
+define('BX_CMT_ORDER_BY_POPULAR', 'popular');
+
+define('BX_CMT_FILTER_ALL', 'all');
+define('BX_CMT_FILTER_OTHERS', 'others');
+define('BX_CMT_FILTER_FRIENDS', 'friends');
+define('BX_CMT_FILTER_SUBSCRIPTIONS', 'subscriptions');
+
+define('BX_CMT_ORDER_WAY_ASC', 'asc');
+define('BX_CMT_ORDER_WAY_DESC', 'desc');
+
+define('BX_CMT_PFP_TOP', 'top');
+define('BX_CMT_PFP_BOTTOM', 'bottom');
+
+define('BX_CMT_RATE_VALUE_PLUS', 1);
+define('BX_CMT_RATE_VALUE_MINUS', -1);
+
+/**
+ * @page objects
+ * @section comments Comments
+ * @ref BxDolCmts
+ */
+
+/**
+ * Comments for any content
+ *
+ * Related classes:
+ * - BxDolCmtsQuery - comments database queries
+ * - BxBaseCmts - comments base representation
+ * - BxTemplCmts - custom template representation
+ *
+ * AJAX comments for any content.
+ * Self moderated - users rate all comments, and if comment is
+ * below viewing treshold it is hidden by default.
+ *
+ * To add comments section to your module you need to add a record to 'sys_objects_cmts' table:
+ *
+ * - ID - autoincremented id for internal usage
+ * - ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ * - TableCmts - table name where comments are stored
+ * - Nl2br - convert new lines to <br /> on saving
+ * - PerView - number of comments on a page
+ * - IsRatable - 0 or 1 allow to rate comments or not
+ * - ViewingThreshold - comment viewing treshost, if comment is below this number it is hidden by default
+ * - IsOn - is this comment object enabled
+ * - RootStylePrefix - toot comments style prefix, if you need root comments look different\
+ * - ObjectVote - Vote object name to process comments' votes. May be empty if Comment Vote is not needed.
+ * - TriggerTable - table to be updated upon each comment
+ * - TriggerFieldId - TriggerTable table field with unique record id of
+ * - TriggerFieldComments - TriggerTable table field with comments count, it will be updated automatically upon eaech comment
+ * - ClassName - your custom class name if you need to override default class, this class must have the same constructor arguments
+ * - ClassFile - file where your ClassName is stored.
+ *
+ * You can refer to BoonEx modules for sample record in this table.
+ *
+ *
+ *
+ * @section example Example of usage:
+ * After filling in the table you can show comments section in any place, using the following code:
+ *
+ * @code
+ * bx_import ('BxTemplCmts');
+ * $o = new BxTemplCmts('value of ObjectName field', $iYourEntryId);
+ * if ($o->isEnabled())
+ *     echo $o->getCommentsFirst ();
+ * @endcode
+ *
+ * Please note that you never need to use BxDolCmts class directly, use BxTemplCmts instead.
+ * Also if you override comments class with your own then make it child of BxTemplCmts class.
+ *
+ *
+ *
+ * @section acl Memberships/ACL:
+ * - comments post
+ * - comments edit own
+ * - comments remove own
+ * - comments edit all
+ *
+ *
+ *
+ * @section alerts Alerts:
+ * Alerts type/unit - every module has own type/unit, it equals to ObjectName.
+ *
+ * The following alerts are rised
+ *
+ * - commentPost - comment was posted
+ *      - $iObjectId - entry id
+ *      - $iSenderId - author of comment
+ *      - $aExtra['comment_id'] - just added comment id
+ *
+ * - commentRemoved - comments was removed
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment deleter id
+ *      - $aExtra['comment_id'] - removed comment id
+ *
+ * - commentUpdated - comments was updated
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment deleter id
+ *      - $aExtra['comment_id'] - updated comment id
+ *
+ * - commentRated - comments was rated
+ *      - $iObjectId - entry id
+ *      - $iSenderId - comment rater id
+ *      - $aExtra['comment_id'] - rated comment id
+ *      - $aExtra['rate'] - comment rate 1 or -1
+ *
+ */
+class BxDolCmts extends BxDol implements iBxDolReplaceable
+{
+    protected $_oQuery = null;
+
+    protected $_sFormObject;
+    protected $_sFormDisplayPost;
+    protected $_sFormDisplayEdit;
+
+    protected $_sTableImages;
+    protected $_sTableImages2Entries;
+
+    protected $_sConnObjFriends;
+    protected $_sConnObjSubscriptions;
+
+    protected $_sMenuObjManage;
+    protected $_sMenuObjActions;
+
+    protected $_sViewUrl = '';
+    protected $_sBaseUrl = '';
+    protected $_sListAnchor = '';
+
+    protected $_sSystem = 'profile'; ///< current comment system name
+    protected $_aSystem = array (); ///< current comments system array
+    protected $_iId = 0; ///< obect id to be commented
+
+    protected $_aMarkers = array ();
+
+    protected $_sDisplayType = '';
+    protected $_sDpSessionKey = '';
+    protected $_iDpMaxLevel = 0;
+
+    protected $_sBrowseType = '';
+    protected $_sBrowseFilter = '';
+    protected $_sBpSessionKeyType = '';
+    protected $_sBpSessionKeyFilter = '';
+    protected $_aOrder = array();
+
+    protected $_sSnippetLenthLiveSearch = 50;
+
+    protected $_iRememberTime = 2592000;
+
+    /**
+     * Constructor
+     * $sSystem - comments system name
+     * $iId - obect id to be commented
+     */
+    function __construct($sSystem, $iId, $iInit = 1)
+    {
+        parent::__construct();
+
+        $this->_aSystems = $this->getSystems();
+        if(!isset($this->_aSystems[$sSystem]))
+            return;
+
+        $this->_sSystem = $sSystem;
+        $this->_aSystem = $this->_aSystems[$sSystem];
+
+        $this->_aSystem['table_images'] = 'sys_cmts_images';
+        $this->_aSystem['table_images2entries'] = 'sys_cmts_images2entries';
+
+        $this->_iDpMaxLevel = (int)$this->_aSystem['number_of_levels'];
+        $this->_sDisplayType = $this->_iDpMaxLevel == 0 ? BX_CMT_DISPLAY_FLAT : BX_CMT_DISPLAY_THREADED;
+        $this->_sDpSessionKey = 'bx_' . $this->_sSystem . '_dp_';
+
+        $this->_sBrowseType = $this->_aSystem['browse_type'];
+        $this->_sBrowseFilter = BX_CMT_FILTER_ALL;
+        $this->_sBpSessionKeyType = 'bx_' . $this->_sSystem . '_bpt_';
+        $this->_sBpSessionKeyFilter = 'bx_' . $this->_sSystem . '_bpf_';
+        $this->_aOrder = array(
+            'by' => BX_CMT_ORDER_BY_DATE,
+            'way' => BX_CMT_ORDER_WAY_ASC
+        );
+
+        list($mixedUserDp, $mixedUserBpType, $mixedUserBpFilter) = $this->_getUserChoice();
+        if(!empty($mixedUserDp))
+            $this->_sDisplayType = $mixedUserDp;
+        if(!empty($mixedUserBpType))
+            $this->_sBrowseType = $mixedUserBpType;
+        if(!empty($mixedUserBpFilter))
+            $this->_sBrowseFilter = $mixedUserBpFilter;
+
+        $this->_sViewUrl = BX_DOL_URL_ROOT . 'cmts.php';
+        $this->_sBaseUrl = BxDolPermalinks::getInstance()->permalink($this->_aSystem['base_url']);
+        if(get_mb_substr($this->_sBaseUrl, 0, 4) != 'http')
+            $this->_sBaseUrl = BX_DOL_URL_ROOT . $this->_sBaseUrl;
+        $this->_sListAnchor = "cmts-anchor-%s-%d";
+
+        $this->_oQuery = new BxDolCmtsQuery($this);
+
+        $this->_sFormObject = 'sys_comment';
+        $this->_sFormDisplayPost = 'sys_comment_post';
+        $this->_sFormDisplayEdit = 'sys_comment_edit';
+
+        $this->_sConnObjFriends = 'sys_profiles_friends';
+        $this->_sConnObjSubscriptions = 'sys_profiles_subscriptions';
+
+        $this->_sMenuObjManage = 'sys_cmts_item_manage';
+        $this->_sMenuObjActions = 'sys_cmts_item_actions';
+
+        if ($iInit)
+            $this->init($iId);
+    }
+
+    /**
+     * get comments object instanse
+     * @param $sSys comments object name
+     * @param $iId associated content id, where comments are postred in
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId]))
+            return $GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId];
+
+        $aSystems = self::getSystems();
+        if (!isset($aSystems[$sSys]))
+            return null;
+
+        bx_import('BxTemplCmts');
+        $sClassName = 'BxTemplCmts';
+        if(!empty($aSystems[$sSys]['class_name'])) {
+            $sClassName = $aSystems[$sSys]['class_name'];
+            if(!empty($aSystems[$sSys]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSys]['class_file']);
+            else
+                bx_import($sClassName);
+        }
+
+        $o = new $sClassName($sSys, $iId, $iInit);
+        return ($GLOBALS['bxDolClasses']['BxDolCmts!' . $sSys . $iId] = $o);
+    }
+
+    public static function &getSystems ()
+    {
+        if (!isset($GLOBALS['bx_dol_cmts_systems'])) {
+            $GLOBALS['bx_dol_cmts_systems'] = BxDolDb::getInstance()->fromCache('sys_objects_cmts', 'getAllWithKey', '
+                SELECT
+                    `ID` as `system_id`,
+                    `Name` AS `name`,
+                    `Table` AS `table`,
+                    `CharsPostMin` AS `chars_post_min`,
+                    `CharsPostMax` AS `chars_post_max`,
+                    `CharsDisplayMax` AS `chars_display_max`,
+                    `Nl2br` AS `nl2br`,
+                    `PerView` AS `per_view`,
+                    `PerViewReplies` AS `per_view_replies`,
+                    `BrowseType` AS `browse_type`,
+                    `IsBrowseSwitch` AS `is_browse_switch`,
+                    `PostFormPosition` AS `post_form_position`,
+                    `NumberOfLevels` AS `number_of_levels`,
+                    `IsDisplaySwitch` AS `is_display_switch`,
+                    `IsRatable` AS `is_ratable`,
+                    `ViewingThreshold` AS `viewing_threshold`,
+                    `IsOn` AS `is_on`,
+                    `RootStylePrefix` AS `root_style_prefix`,
+                    `BaseUrl` AS `base_url`,
+                    `ObjectVote` AS `object_vote`,
+                    `TriggerTable` AS `trigger_table`,
+                    `TriggerFieldId` AS `trigger_field_id`,
+                    `TriggerFieldTitle` AS `trigger_field_title`,
+                    `TriggerFieldComments` AS `trigger_field_comments`,
+                    `ClassName` AS `class_name`,
+                    `ClassFile` AS `class_file`
+                FROM `sys_objects_cmts`', 'name');
+        }
+        return $GLOBALS['bx_dol_cmts_systems'];
+    }
+
+    public function init ($iId)
+    {
+        if (empty($this->iId) && $iId)
+            $this->setId($iId);
+
+        $this->addMarkers(array(
+            'object_id' => $this->getId(),
+            'user_id' => $this->_getAuthorId()
+        ));
+    }
+
+    public function getId ()
+    {
+        return $this->_iId;
+    }
+
+    public function isEnabled ()
+    {
+        return isset($this->_aSystem['is_on']) && $this->_aSystem['is_on'];
+    }
+
+    public function getSystemId()
+    {
+        return $this->_aSystem['system_id'];
+    }
+
+    public function getSystemName()
+    {
+        return $this->_sSystem;
+    }
+
+    public function getStorageObjectName()
+    {
+    	return $this->_getFormObject()->getStorageObjectName();
+    }
+
+	public function getTranscoderPreviewName()
+    {
+    	return $this->_getFormObject()->getTranscoderPreviewName();
+    }
+
+    public function getSystemInfo()
+    {
+        return $this->_aSystem;
+    }
+
+    public function getMaxLevel()
+    {
+        return $this->_iDpMaxLevel;
+    }
+
+    public function getOrder ()
+    {
+        return $this->_sOrder;
+    }
+
+    public function getPerView ($iCmtParentId = 0)
+    {
+        return $iCmtParentId == 0 ? $this->_aSystem['per_view'] : $this->_aSystem['per_view_replies'];
+    }
+
+    public function getBaseUrl()
+    {
+        return $this->_replaceMarkers($this->_sBaseUrl);
+    }
+
+    public function getListUrl()
+    {
+        return $this->getBaseUrl() . '#' . $this->getListAnchor();
+    }
+
+    public function getListAnchor()
+    {
+        return sprintf($this->_sListAnchor, str_replace('_', '-', $this->getSystemName()), $this->getId());
+    }
+
+	public function getViewUrl($iCmtId)
+    {
+    	if(empty($this->_aSystem['trigger_field_title']))
+    		return '';
+
+    	return bx_append_url_params($this->_sViewUrl, array(
+			'sys' => $this->_sSystem,
+			'id' => $this->_iId,
+			'cmt_id' => $iCmtId
+		));
+    }
+
+    public function getConnectionObject($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_CMT_FILTER_FRIENDS:
+                $sResult = $this->_sConnObjFriends;
+                break;
+            case BX_CMT_FILTER_SUBSCRIPTIONS:
+                $sResult = $this->_sConnObjSubscriptions;
+                break;
+        }
+
+        return $sResult;
+    }
+
+    public function getVoteObject($iId)
+    {
+        if(empty($this->_aSystem['object_vote']))
+            return false;
+
+        bx_import('BxDolVote');
+        $oVote = BxDolVote::getObjectInstance($this->_aSystem['object_vote'], $iId);
+        if(!$oVote->isEnabled())
+            return false;
+
+        return $oVote;
+    }
+
+    public function isNl2br ()
+    {
+        return $this->_aSystem['nl2br'];
+    }
+
+    public function isRatable ()
+    {
+        return $this->_aSystem['is_ratable'];
+    }
+
+    public function isAttachImageEnabled()
+    {
+        return true;
+    }
+
+    /**
+     * set id to operate with votes
+     */
+    public function setId ($iId)
+    {
+        if ($iId == $this->getId()) return;
+        $this->_iId = $iId;
+    }
+
+    /**
+     * Add replace markers.
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Database functions
+     */
+    public function getQueryObject ()
+    {
+        return $this->_oQuery;
+    }
+
+    public function getCommentsTableName ()
+    {
+        return $this->_oQuery->getTableName ();
+    }
+
+    public function getObjectTitle ($iObjectId = 0)
+    {
+        return $this->_oQuery->getObjectTitle ($iObjectId ? $iObjectId : $this->getId());
+    }
+
+    public function getCommentsCount ($iObjectId = 0, $iCmtVParentId = 0, $sFilter = '')
+    {
+        return $this->_oQuery->getCommentsCount ($iObjectId ? $iObjectId : $this->getId(), $iCmtVParentId, $this->_getAuthorId(), $sFilter);
+    }
+
+    public function getCommentsArray ($iVParentId, $sFilter, $aOrder, $iStart = 0, $iCount = -1)
+    {
+        return $this->_oQuery->getComments ($this->getId(), $iVParentId, $this->_getAuthorId(), $sFilter, $aOrder, $iStart, $iCount);
+    }
+
+    public function getCommentRow ($iCmtId)
+    {
+        return $this->_oQuery->getComment ($this->getId(), $iCmtId);
+    }
+
+    public function onObjectDelete ($iObjectId = 0)
+    {
+        // delete comments
+        $aFiles = array();
+        $this->_oQuery->deleteObjectComments ($iObjectId ? $iObjectId : $this->getId(), $aFiles);
+
+        // delete files
+        if ($aFiles) {
+            bx_import('BxDolStorage');
+            $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+            if ($oStorage)
+                $oStorage->queueFilesForDeletion($aFiles);
+        }
+    }
+
+    public static function onAuthorDelete ($iAuthorId)
+    {
+        bx_import('BxDolStorage');
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            $o = self::getObjectInstance($sSystem, 0);
+            $oQuery = $o->getQueryObject();
+
+            // delete comments
+            $aFiles = array ();
+            $oQuery->deleteAuthorComments($iAuthorId, $aFiles);
+
+            // delete files
+            $oStorage = BxDolStorage::getObjectInstance($o->getStorageObjectName());
+            if ($oStorage)
+                $oStorage->queueFilesForDeletion($aFiles);
+        }
+        return true;
+    }
+
+    public static function onModuleUninstall ($sModuleName, &$iFiles = null)
+    {
+        bx_import('BxDolStorage');
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem) {
+            if (0 != strncasecmp($sModuleName, $sSystem, strlen($sModuleName)))
+                continue;
+
+            $o = self::getObjectInstance($sSystem, 0);
+            $oQuery = $o->getQueryObject();
+
+            // delete comments
+            $aFiles = array ();
+            $oQuery->deleteAll($aSystem['system_id'], $aFiles);
+
+            // delete files
+            $oStorage = BxDolStorage::getObjectInstance($o->getStorageObjectName());
+            if ($oStorage && $aFiles)
+                $oStorage->queueFilesForDeletion($aFiles);
+
+            if (null !== $iFiles)
+                $iFiles += count($aFiles);
+        }
+
+        return true;
+    }
+
+    /**
+     * Permissions functions
+     */
+    public function checkAction ($sAction, $isPerformAction = false)
+    {
+        $iId = $this->_getAuthorId();
+        $a = checkActionModule($iId, $sAction, 'system', $isPerformAction);
+        return $a[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkActionErrorMsg ($sAction)
+    {
+        $iId = $this->_getAuthorId();
+        $a = checkActionModule($iId, $sAction, 'system');
+        return $a[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED ? $a[CHECK_ACTION_MESSAGE] : '';
+    }
+
+    public function isVoteAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(!$this->isRatable())
+            return false;
+
+        $oVote = $this->getVoteObject($aCmt['cmt_id']);
+        if($oVote === false)
+            return false;
+
+        $iUserId = (int)$this->_getAuthorId();
+        if($iUserId == 0)
+            return false;
+
+        if(isAdmin())
+            return true;
+
+        return $oVote->isAllowedVote($isPerformAction);
+    }
+
+    public function isPostReplyAllowed ($isPerformAction = false)
+    {
+        return $this->checkAction ('comments post', $isPerformAction);
+    }
+
+    public function msgErrPostReplyAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments post');
+    }
+
+    public function isEditAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        if($aCmt['cmt_author_id'] == $this->_getAuthorId() && $this->checkAction ('comments edit own', $isPerformAction))
+            return true;
+
+        return $this->checkAction('comments edit all', $isPerformAction);
+    }
+
+    public function msgErrEditAllowed ()
+    {
+        return $this->checkActionErrorMsg ('comments edit own');
+    }
+
+    public function isRemoveAllowed ($aCmt, $isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        if($aCmt['cmt_author_id'] == $this->_getAuthorId() && $this->checkAction ('comments remove own', $isPerformAction))
+            return true;
+
+        return $this->checkAction ('comments remove all', $isPerformAction);
+    }
+
+    public function msgErrRemoveAllowed ()
+    {
+        return $this->checkActionErrorMsg('comments remove own');
+    }
+
+    /**
+     * Actions functions
+     */
+    public function actionGetFormPost ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        $iCmtParentId= isset($_REQUEST['CmtParent']) ? bx_process_input($_REQUEST['CmtParent'], BX_DATA_INT) : 0;
+        $sCmtBrowse = isset($_REQUEST['CmtBrowse']) ? bx_process_input($_REQUEST['CmtBrowse'], BX_DATA_TEXT) : '';
+        $sCmtDisplay = isset($_REQUEST['CmtDisplay']) ? bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT) : '';
+
+        return $this->getFormBoxPost(array('parent_id' => $iCmtParentId, 'type' => $sCmtBrowse), array('type' => $sCmtDisplay));
+    }
+
+    public function actionGetFormEdit ()
+    {
+        if (!$this->isEnabled()){
+            $this->_echoResultJson(array());
+            return;
+        }
+
+        $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+        $this->_echoResultJson($this->getFormEdit($iCmtId));
+    }
+
+    public function actionGetCmt ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        $iCmtId = bx_process_input($_REQUEST['Cmt'], BX_DATA_INT);
+        $sCmtBrowse = isset($_REQUEST['CmtBrowse']) ? bx_process_input($_REQUEST['CmtBrowse'], BX_DATA_TEXT) : '';
+        $sCmtDisplay = isset($_REQUEST['CmtDisplay']) ? bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT) : '';
+
+        $aCmt = $this->getCommentRow($iCmtId);
+        $this->_echoResultJson(array(
+            'parent_id' => $aCmt['cmt_parent_id'],
+            'vparent_id' => $aCmt['cmt_vparent_id'],
+            'content' => $this->getComment($aCmt, array('type' => $sCmtBrowse), array('type' => $sCmtDisplay))
+        ));
+    }
+
+    public function actionGetCmts ()
+    {
+        if (!$this->isEnabled())
+            return '';
+
+        $iCmtVParentId = bx_process_input($_REQUEST['CmtParent'], BX_DATA_INT);
+        $iCmtStart = isset($_REQUEST['CmtStart']) ? bx_process_input($_REQUEST['CmtStart'], BX_DATA_INT) : -1;
+        $iCmtPerView = isset($_REQUEST['CmtPerView']) ? bx_process_input($_REQUEST['CmtPerView'], BX_DATA_INT) : -1;
+        $sCmtBrowse = isset($_REQUEST['CmtBrowse']) ? bx_process_input($_REQUEST['CmtBrowse'], BX_DATA_TEXT) : '';
+        $sCmtDisplay = isset($_REQUEST['CmtDisplay']) ? bx_process_input($_REQUEST['CmtDisplay'], BX_DATA_TEXT) : '';
+        $sCmtFilter = isset($_REQUEST['CmtFilter']) ? bx_process_input($_REQUEST['CmtFilter'], BX_DATA_TEXT) : '';
+
+        return $this->getComments(array('vparent_id' => $iCmtVParentId, 'start' => $iCmtStart, 'per_view' => $iCmtPerView, 'type' => $sCmtBrowse, 'filter' => $sCmtFilter), array('type' => $sCmtDisplay, 'show_empty' => true));
+    }
+
+    public function actionSubmitPostForm()
+    {
+        if(!$this->isEnabled() || !$this->isPostReplyAllowed()) {
+            $this->_echoResultJson(array());
+            return;
+        }
+
+        $iCmtParentId = 0;
+        if(bx_get('cmt_parent_id') !== false)
+            $iCmtParentId = bx_process_input(bx_get('cmt_parent_id'), BX_DATA_INT);
+
+        $this->_echoResultJson($this->getFormPost($iCmtParentId));
+    }
+
+    public function actionSubmitEditForm()
+    {
+        if (!$this->isEnabled()) {
+            $this->_echoResultJson(array());
+            return;
+        };
+
+        $iCmtId = 0;
+        if(bx_get('cmt_id') !== false)
+            $iCmtId = bx_process_input(bx_get('cmt_id'), BX_DATA_INT);
+
+        $this->_echoResultJson($this->getFormEdit($iCmtId));
+    }
+
+    public function actionRemove()
+    {
+        if (!$this->isEnabled()) {
+            $this->_echoResultJson(array());
+            return;
+        };
+
+        $iCmtId = 0;
+        if(bx_get('Cmt') !== false)
+            $iCmtId = bx_process_input(bx_get('Cmt'), BX_DATA_INT);
+
+        $aCmt = $this->_oQuery->getCommentSimple ($this->getId(), $iCmtId);
+        if(!$aCmt) {
+            $this->_echoResultJson(array('msg' => _t('_No such comment')));
+            return;
+        }
+
+        if ($aCmt['cmt_replies'] > 0) {
+            $this->_echoResultJson(array('msg' => _t('_Can not delete comments with replies')));
+            return;
+        }
+
+        $iCmtAuthorId = $this->_getAuthorId();
+        if(!$this->isRemoveAllowed($aCmt)) {
+            $this->_echoResultJson(array('msg' => $aCmt['cmt_author_id'] == $iCmtAuthorId ? strip_tags($this->msgErrRemoveAllowed()) : _t('_Access denied')));
+            return;
+        }
+
+        if($this->_oQuery->removeComment ($this->getId(), $aCmt['cmt_id'], $aCmt['cmt_parent_id'])) {
+            $this->_triggerComment();
+
+            bx_import('BxDolStorage');
+            $oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+
+            $aImages = $this->_oQuery->getImages($this->_aSystem['system_id'], $aCmt['cmt_id']);
+            foreach($aImages as $aImage)
+                $oStorage->deleteFile($aImage['image_id']);
+
+            $this->_oQuery->deleteImages($this->_aSystem['system_id'], $aCmt['cmt_id']);
+
+            $this->isRemoveAllowed(true);
+
+            bx_import('BxDolAlerts');
+            $oZ = new BxDolAlerts($this->_sSystem, 'commentRemoved', $this->getId(), $iCmtAuthorId, array('comment_id' => $aCmt['cmt_id'], 'comment_author_id' => $aCmt['cmt_author_id']));
+            $oZ->alert();
+
+            $this->_echoResultJson(array('id' => $iCmtId));
+            return;
+        }
+
+        $this->_echoResultJson(array('msg' => _t('_cmt_err_cannot_perform_action')));
+    }
+
+    /**
+     * Internal functions
+     */
+    protected function _getAuthorId ()
+    {
+        return isMember() ? bx_get_logged_profile_id() : 0;
+    }
+
+    protected function _getAuthorPassword ()
+    {
+        return isMember() ? $_COOKIE['memberPassword'] : "";
+    }
+
+    protected function _getAuthorIp ()
+    {
+        return getVisitorIP();
+    }
+
+
+    protected function _getAuthorInfo($iAuthorId = 0)
+    {
+        $oProfile = $this->_getAuthorObject($iAuthorId);
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit()
+        );
+    }
+
+    protected function _getAuthorObject($iAuthorId = 0)
+    {
+        bx_import('BxDolProfile');
+        $oProfile = BxDolProfile::getInstance($iAuthorId);
+        if (!$oProfile) {
+            bx_import('BxDolProfileUndefined');
+            $oProfile = BxDolProfileUndefined::getInstance();
+        }
+
+        return $oProfile;
+    }
+
+	protected function _getFormObject($sAction = BX_CMT_ACTION_POST)
+    {
+        $sDisplayName = '_sFormDisplay' . ucfirst($sAction);
+
+        bx_import('BxDolForm');
+        return BxDolForm::getObjectInstance($this->_sFormObject, $this->$sDisplayName);
+    }
+
+    protected function _prepareTextForEdit ($s)
+    {
+        if ($this->isNl2br())
+            return htmlspecialchars_decode(str_replace('<br />', "", $s));
+
+        return $s;
+    }
+
+    protected function _prepareTextForSave ($s)
+    {
+        $iDataAction = $this->isNl2br() ? BX_DATA_TEXT_MULTILINE : BX_DATA_HTML;
+        return bx_process_input($s, $iDataAction);
+    }
+
+    protected function _prepareTextForOutput ($s)
+    {
+        $sHttp = '';
+        $sPattern = "/((https?|ftp|news):\/\/)?([a-z]([a-z0-9\-]*\.)+(aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|[a-z]{2})|(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&amp;]*)?)?(#[a-z][a-z0-9_]*)?/";
+        $aMatches = array();
+        if(preg_match($sPattern, $s, $aMatches) && empty($aMatches[1]))
+            $sHttp = 'http://';
+
+        $s = bx_process_output($s, BX_DATA_HTML);
+        $s = preg_replace($sPattern, '<a href="' . $sHttp . '$0" target="_blank">$0</a>', $s);
+
+        return $s;
+    }
+
+    protected function _prepareParams(&$aBp, &$aDp)
+    {
+        $aBp['type'] = isset($aBp['type']) && !empty($aBp['type']) ? $aBp['type'] : $this->_sBrowseType;
+        $aBp['filter'] = isset($aBp['filter']) && !empty($aBp['filter']) ? $aBp['filter'] : $this->_sBrowseFilter;
+        $aBp['parent_id'] = isset($aBp['parent_id']) ? $aBp['parent_id'] : 0;
+        $aBp['start'] = isset($aBp['start']) ? $aBp['start'] : -1;
+        $aBp['per_view'] = isset($aBp['per_view']) ? $aBp['per_view'] : -1;
+        $aBp['order']['by'] = isset($aBp['order_by']) ? $aBp['order_by'] : $this->_aOrder['by'];
+        $aBp['order']['way'] = isset($aBp['order_way']) ? $aBp['order_way'] : $this->_aOrder['way'];
+
+        $aDp['type'] = isset($aDp['type']) && !empty($aDp['type']) ? $aDp['type'] : $this->_sDisplayType;
+
+        switch($aDp['type']) {
+            case BX_CMT_DISPLAY_FLAT:
+                $aBp['vparent_id'] = -1;
+                $aBp['per_view'] = $aBp['per_view'] != -1 ? $aBp['per_view'] : $this->getPerView(0);
+                break;
+
+            case BX_CMT_DISPLAY_THREADED:
+                $aBp['per_view'] = $aBp['per_view'] != -1 ? $aBp['per_view'] : $this->getPerView($aBp['vparent_id']);
+                break;
+        }
+
+        switch ($aBp['type']) {
+            case BX_CMT_BROWSE_POPULAR:
+                $aBp['order'] = array(
+                    'by' => BX_CMT_ORDER_BY_POPULAR,
+                    'way' => BX_CMT_ORDER_WAY_DESC
+                );
+                break;
+        }
+
+        $aBp['count'] = $this->getCommentsCount($this->_iId, $aBp['vparent_id'], $aBp['filter']);
+        if($aBp['start'] != -1)
+            return;
+
+        $aBp['start'] = 0;
+        if($aBp['type'] == BX_CMT_BROWSE_TAIL) {
+            $aBp['start'] = $aBp['count'] - $aBp['per_view'];
+            if($aBp['start'] < 0) {
+                $aBp['per_view'] += $aBp['start'];
+                $aBp['start'] = 0;
+            }
+        }
+
+        $this->_setUserChoice($aDp['type'], $aBp['type'], $aBp['filter']);
+    }
+
+    protected function _triggerComment()
+    {
+        if(!$this->_aSystem['trigger_table'])
+            return false;
+
+        $iId = $this->getId();
+        if(!$iId)
+            return false;
+
+        $iCount = $this->getCommentsCount($iId);
+        return $this->_oQuery->updateTriggerTable($iId, $iCount);
+    }
+
+    /**
+     * Replace provided markers in a string
+     * @param $mixed string or array to replace markers in
+     * @return string where all occured markers are replaced
+     */
+    protected function _replaceMarkers ($mixed)
+    {
+        return bx_replace_markers($mixed, $this->_aMarkers);
+    }
+
+    protected function _getUserChoice()
+    {
+        $mixedDp = $mixedBpType = $mixedBpFilter = false;
+        if(!isLogged())
+            return array($mixedDp, $mixedBpType, $mixedBpFilter);
+
+        $iUserId = $this->_getAuthorId();
+
+        bx_import('BxDolSession');
+        $oSession = BxDolSession::getInstance();
+
+        $mixedDp = $oSession->getValue($this->_sDpSessionKey . $iUserId);
+        $mixedBpType = $oSession->getValue($this->_sBpSessionKeyType . $iUserId);
+        $mixedBpFilter = $oSession->getValue($this->_sBpSessionKeyFilter . $iUserId);
+
+        return array($mixedDp, $mixedBpType, $mixedBpFilter);
+    }
+
+    protected function _setUserChoice($sDp, $sBpType, $sBpFilter)
+    {
+        if(!isLogged())
+            return;
+
+        $iUserId = $this->_getAuthorId();
+
+        bx_import('BxDolSession');
+        $oSession = BxDolSession::getInstance();
+
+        if(!empty($sDp))
+            $oSession->setValue($this->_sDpSessionKey . $iUserId, $sDp);
+
+        if(!empty($sBpType))
+            $oSession->setValue($this->_sBpSessionKeyType . $iUserId, $sBpType);
+
+        if(!empty($sBpFilter))
+            $oSession->setValue($this->_sBpSessionKeyFilter . $iUserId, $sBpFilter);
+    }
+
+    protected function _sendNotificationEmail($iCmtId, $iCmtParentId)
+    {
+        $aCmt = $this->getCommentRow($iCmtId);
+        $aCmtParent = $this->getCommentRow($iCmtParentId);
+        if(empty($aCmt) || !is_array($aCmt) || empty($aCmtParent) || !is_array($aCmtParent) || (int)$aCmt['cmt_author_id'] == (int)$aCmtParent['cmt_author_id'])
+            return;
+
+        $oProfile = $this->_getAuthorObject($aCmtParent['cmt_author_id']);
+
+        bx_import('BxDolAccount');
+        $iAccount = $oProfile->getAccountId();
+        $aAccount = BxDolAccount::getInstance($iAccount)->getInfo();
+
+        $aPlus = array();
+        $aPlus['reply_text'] = bx_process_output($aCmt['cmt_text']);
+        $aPlus['comment_url'] = sprintf('%scmts.php?sys=%s&id=1&cmt_id=%d', BX_DOL_URL_ROOT, $this->_sSystem, $iCmtParentId);
+
+        bx_import('BxDolEmailTemplates');
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_CommentReplied', $aPlus);
+        return $aTemplate && sendMail($aAccount['email'], $aTemplate['Subject'], $aTemplate['Body']);
+    }
+}
+
+/** @} */

+ 313 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolCmtsQuery.php

@@ -0,0 +1,313 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolDb');
+
+/**
+ * @see BxDolCmts
+ */
+class BxDolCmtsQuery extends BxDolDb
+{
+    protected $_oMain;
+
+    protected $_sTable;
+    protected $_sTriggerTable;
+    protected $_sTriggerFieldId;
+    protected $_sTriggerFieldTitle;
+    protected $_sTriggerFieldComments;
+
+    protected $_sTableImages;
+    protected $_sTableImages2Entries;
+
+    function __construct(&$oMain)
+    {
+        $this->_oMain = $oMain;
+
+        $aSystem = $this->_oMain->getSystemInfo();
+        $this->_sTable = $aSystem['table'];
+        $this->_sTriggerTable = $aSystem['trigger_table'];
+        $this->_sTriggerFieldId = $aSystem['trigger_field_id'];
+        $this->_sTriggerFieldTitle = $aSystem['trigger_field_title'];
+        $this->_sTriggerFieldComments = $aSystem['trigger_field_comments'];
+
+        $this->_sTableImages = $aSystem['table_images'];
+        $this->_sTableImages2Entries = $aSystem['table_images2entries'];
+
+        parent::__construct();
+    }
+
+    function getTableName ()
+    {
+        return $this->_sTable;
+    }
+
+    function getCommentsCount ($iId, $iCmtVParentId = 0, $iAuthorId = 0, $sFilter = '')
+    {
+        $sWhereClause = '';
+        if((int)$iCmtVParentId >= 0)
+            $sWhereClause .= $this->prepare(" AND `{$this->_sTable}`.`cmt_vparent_id` = ?", $iCmtVParentId);
+
+        $sJoinClause = '';
+        switch($sFilter) {
+        	case BX_CMT_FILTER_FRIENDS:
+        	case BX_CMT_FILTER_SUBSCRIPTIONS:
+	            bx_import('BxDolConnection');
+	            $oConnection = BxDolConnection::getObjectInstance($this->_oMain->getConnectionObject($sFilter));
+	
+	            $aQueryParts = $oConnection->getConnectedContentAsSQLParts($this->_sTable, 'cmt_author_id', $iAuthorId);
+	            $sJoinClause .= ' ' . $aQueryParts['join'];
+	            break;
+
+        	case BX_CMT_FILTER_OTHERS:
+        		$sWhereClause .= $this->prepare(" AND `{$this->_sTable}`.`cmt_author_id` <> ?", $iAuthorId);
+        		break;
+        }
+
+        $sQuery = $this->prepare("SELECT
+                COUNT(*)
+            FROM `{$this->_sTable}`
+            $sJoinClause
+            WHERE `{$this->_sTable}`.`cmt_object_id` = ?" . $sWhereClause, $iId);
+
+        return (int)$this->getOne($sQuery);
+    }
+
+    function getComments ($iId, $iCmtVParentId = 0, $iAuthorId = 0, $sFilter = '', $aOrder = array(), $iStart = 0, $iCount = -1)
+    {
+        $sFields = $sJoin = "";
+
+        $oVote = $this->_oMain->getVoteObject(0);
+        if($oVote !== false) {
+            $aSql = $oVote->getSqlParts($this->_sTable, 'cmt_id');
+
+            $sFields .= $aSql['fields'];
+            $sJoin .= $aSql['join'];
+        }
+
+        $sWhereParent = '';
+        if((int)$iCmtVParentId >= 0)
+            $sWhereParent = $this->prepare(" AND `{$this->_sTable}`.`cmt_vparent_id` = ?", $iCmtVParentId);
+
+        if(in_array($sFilter, array(BX_CMT_FILTER_FRIENDS, BX_CMT_FILTER_SUBSCRIPTIONS))) {
+            bx_import('BxDolConnection');
+            $oConnection = BxDolConnection::getObjectInstance($this->_oMain->getConnectionObject($sFilter));
+
+            $aQueryParts = $oConnection->getConnectedContentAsSQLParts($this->_sTable, 'cmt_author_id', $iAuthorId);
+            $sJoin .= ' ' . $aQueryParts['join'];
+        }
+
+        $sOder = " ORDER BY `{$this->_sTable}`.`cmt_time` ASC";
+        if(isset($aOrder['by']) && isset($aOrder['way'])) {
+            $aOrder['way'] = strtoupper(in_array($aOrder['way'], array(BX_CMT_ORDER_WAY_ASC, BX_CMT_ORDER_WAY_DESC)) ? $aOrder['way'] : BX_CMT_ORDER_WAY_ASC);
+
+            switch($aOrder['by']) {
+                case BX_CMT_ORDER_BY_DATE:
+                    $sOder = " ORDER BY `{$this->_sTable}`.`cmt_time` " . $aOrder['way'];
+                    break;
+
+                case BX_CMT_ORDER_BY_POPULAR:
+                    $sOder = " ORDER BY `{$this->_sTable}`.`cmt_rate` " . $aOrder['way'];
+                    break;
+            }
+        }
+
+        $sLimit = $iCount != -1 ? $this->prepare(" LIMIT ?, ?", (int)$iStart, (int)$iCount) : '';
+
+        $sQuery = $this->prepare("SELECT
+                `{$this->_sTable}`.`cmt_id`,
+                `{$this->_sTable}`.`cmt_parent_id`,
+                `{$this->_sTable}`.`cmt_vparent_id`,
+                `{$this->_sTable}`.`cmt_object_id`,
+                `{$this->_sTable}`.`cmt_author_id`,
+                `{$this->_sTable}`.`cmt_level`,
+                `{$this->_sTable}`.`cmt_text`,
+                `{$this->_sTable}`.`cmt_replies`,
+                `{$this->_sTable}`.`cmt_time`
+                $sFields
+            FROM `{$this->_sTable}`
+            $sJoin
+            WHERE `{$this->_sTable}`.`cmt_object_id` = ?" . $sWhereParent . $sOder . $sLimit, $iId);
+
+        return $this->getAll($sQuery);
+    }
+
+    function getComment ($iId, $iCmtId)
+    {
+        $sFields = $sJoin = "";
+
+        $oVote = $this->_oMain->getVoteObject($iCmtId);
+        if($oVote !== false) {
+            $aSql = $oVote->getSqlParts($this->_sTable, 'cmt_id');
+
+            $sFields .= $aSql['fields'];
+            $sJoin .= $aSql['join'];
+        }
+
+        $sQuery = $this->prepare("SELECT
+                `{$this->_sTable}`.`cmt_id`,
+                `{$this->_sTable}`.`cmt_parent_id`,
+                `{$this->_sTable}`.`cmt_vparent_id`,
+                `{$this->_sTable}`.`cmt_object_id`,
+                `{$this->_sTable}`.`cmt_author_id`,
+                `{$this->_sTable}`.`cmt_level`,
+                `{$this->_sTable}`.`cmt_text`,
+                `{$this->_sTable}`.`cmt_replies`,
+                `{$this->_sTable}`.`cmt_time`
+                $sFields
+            FROM `{$this->_sTable}`
+            $sJoin
+            WHERE `{$this->_sTable}`.`cmt_object_id` = ? AND `{$this->_sTable}`.`cmt_id` = ?
+            LIMIT 1", $iId, $iCmtId);
+        return $this->getRow($sQuery);
+    }
+
+    function getCommentSimple ($iId, $iCmtId)
+    {
+        $sQuery = $this->prepare("SELECT * FROM {$this->_sTable} AS `c` WHERE `cmt_object_id` = ? AND `cmt_id` = ? LIMIT 1", $iId, $iCmtId);
+        return $this->getRow($sQuery);
+    }
+
+    function removeComment ($iId, $iCmtId, $iCmtParentId)
+    {
+        $sQuery = $this->prepare("DELETE FROM {$this->_sTable} WHERE `cmt_object_id` = ? AND `cmt_id` = ? LIMIT 1", $iId, $iCmtId);
+        if (!$this->query($sQuery))
+            return false;
+
+        if($iCmtParentId)
+            $this->updateRepliesCount($iCmtParentId, -1);
+
+        return true;
+    }
+
+    function saveImages($iSystemId, $iCmtId, $iImageId)
+    {
+        $sQuery = $this->prepare("INSERT IGNORE INTO `{$this->_sTableImages2Entries}` SET `system_id`=?, `cmt_id`=?, `image_id`=?", $iSystemId, $iCmtId, $iImageId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    function getImages($iSystemId, $iCmtId, $iId = false)
+    {
+        $sJoin = '';
+        $sWhere = $this->prepare(" AND `i`.`system_id` = ? ", $iSystemId);
+
+        if (false !== $iCmtId) {
+            $sWhere .= $this->prepare(" AND `i`.`cmt_id` = ? ", $iCmtId);
+        }
+
+        if (false !== $iId) {
+            $sWhere .= $this->prepare(" AND `c`.`cmt_object_id` = ?", $iId);
+            $sJoin .= " INNER JOIN `{$this->_sTable}` AS `c` ON (`i`.`cmt_id` = `c`.`cmt_id`) ";
+        }
+
+        return $this->getAll("SELECT * FROM `{$this->_sTableImages2Entries}` AS `i` " . $sJoin . " WHERE 1 " . $sWhere);
+    }
+
+    function deleteImages($iSystemId, $iCmtId)
+    {
+        $sWhereAddon = '';
+        if (false !== $iCmtId)
+            $sWhereAddon = $this->prepare(" AND `cmt_id` = ? ", $iCmtId);
+
+        $sQuery = $this->prepare("DELETE FROM `{$this->_sTableImages2Entries}` WHERE `system_id` = ?", $iSystemId);
+        $sQuery .= $sWhereAddon;
+
+        return $this->query($sQuery);
+    }
+
+    function updateRepliesCount($iCmtId, $iCount)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `cmt_replies`=`cmt_replies`+? WHERE `cmt_id`=? LIMIT 1", $iCount, $iCmtId);
+        return $this->query($sQuery);
+    }
+
+    function deleteAuthorComments ($iAuthorId, &$aFiles = null)
+    {
+        $aSystem = $this->_oMain->getSystemInfo();
+
+        $isDelOccured = 0;
+        $sQuery = $this->prepare("SELECT `cmt_id`, `cmt_parent_id` FROM {$this->_sTable} WHERE `cmt_author_id` = ? AND `cmt_replies` = 0", $iAuthorId);
+        $a = $this->getAll ($sQuery);
+        for ( reset($a) ; list (, $r) = each ($a) ; ) {
+            $sQuery = $this->prepare("DELETE FROM {$this->_sTable} WHERE `cmt_id` = ?", $r['cmt_id']);
+            $this->query ($sQuery);
+
+            $sQuery = $this->prepare("UPDATE {$this->_sTable} SET `cmt_replies` = `cmt_replies` - 1 WHERE `cmt_id` = ?", $r['cmt_parent_id']);
+            $this->query ($sQuery);
+
+            $aFilesMore = $this->convertImagesArray($this->getImages($aSystem['system_id'], $r['cmt_id']));
+            $this->deleteImages($aSystem['system_id'], $r['cmt_id']);
+            if ($aFilesMore && null !== $aFiles)
+                $aFiles = array_merge($aFiles, $aFilesMore);
+
+            $isDelOccured = 1;
+        }
+        $sQuery = $this->prepare("UPDATE {$this->_sTable} SET `cmt_author_id` = 0 WHERE `cmt_author_id` = ? AND `cmt_replies` != 0", $iAuthorId);
+        $this->query ($sQuery);
+        if ($isDelOccured)
+            $this->query ("OPTIMIZE TABLE {$this->_sTable}");
+    }
+
+    function deleteObjectComments ($iObjectId, &$aFilesReturn = null)
+    {
+        $aSystem = $this->_oMain->getSystemInfo();
+        $aFiles = $this->convertImagesArray($this->getImages($aSystem['system_id'], false, $iObjectId));
+
+        if ($aFiles) {
+            $sQuery = $this->prepare("DELETE FROM {$this->_sTableImages2Entries} WHERE `system_id` = ? ", $aSystem['system_id']);
+            $sQuery .= " AND `image_id` IN(" . $this->implode_escape($aFiles) . ") ";
+            $this->query ($sQuery);
+        }
+
+        $sQuery = $this->prepare("DELETE FROM {$this->_sTable} WHERE `cmt_object_id` = ?", $iObjectId);
+        $this->query ($sQuery);
+        $this->query ("OPTIMIZE TABLE {$this->_sTable}");
+
+        if (null !== $aFilesReturn)
+            $aFilesReturn = $aFiles;
+    }
+
+    function deleteAll ($iSystemId, &$aFiles = null)
+    {
+        // get files
+        if (null !== $aFiles)
+            $aFiles = $this->convertImagesArray($this->getImages($iSystemId, false));
+
+        // delete files
+        $this->deleteImages($iSystemId, false);
+
+        // delete comments
+        $sQuery = $this->prepare("TRUNCATE TABLE {$this->_sTable}");
+        $this->query ($sQuery);
+    }
+
+    function getObjectTitle($iId)
+    {
+        $sQuery = $this->prepare("SELECT `{$this->_sTriggerFieldTitle}` FROM `{$this->_sTriggerTable}` WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iId);
+        return $this->getOne($sQuery);
+    }
+
+    function updateTriggerTable($iId, $iCount)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldComments}` = ? WHERE `{$this->_sTriggerFieldId}` = ? LIMIT 1", $iCount, $iId);
+        return $this->query($sQuery);
+    }
+
+    protected function convertImagesArray($a)
+    {
+        if (!$a || !is_array($a))
+            return array();
+
+        $aFiles = array ();
+        foreach ($a as $aFile)
+            $aFiles[] = $aFile['image_id'];
+        return $aFiles;
+    }
+}
+
+/** @} */

+ 221 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolLiveUpdates.php

@@ -0,0 +1,221 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDol');
+bx_import('BxDolLiveUpdatesQuery');
+
+/**
+ * @page objects
+ * @section live updates Live Updates
+ * @ref BxDolLiveUpdates
+ */
+
+class BxDolLiveUpdates extends BxDol
+{
+	protected $_oQuery;
+    protected $_aSystems;
+
+    protected $_iInterval;
+
+    protected $_sSessionKey;
+
+	protected $_iCacheTTL; // Note. Non-zero value may lead to cache destroying in wrong time.  
+	protected $_sCacheKey;
+
+    protected $_sJsClass;
+    protected $_sJsObject;
+
+    public function __construct()
+    {
+        parent::__construct();
+
+        $this->_oQuery = new BxDolLiveUpdatesQuery();
+        $this->_aSystems = $this->_oQuery->getSystems();
+
+        $this->_iInterval = (int)$this->_oQuery->getParam('sys_live_updates_interval');
+
+        $this->_sSessionKey = 'bx_lu_index';
+        
+        $this->_iCacheTTL = 0;
+        $this->_sCacheKey = 'sys_live_updates_' . bx_get_logged_profile_id();
+
+        $this->_sJsClass = 'BxDolLiveUpdates';
+    	$this->_sJsObject = 'oLiveUpdates';
+    }
+
+    /**
+     * get live updates instanse
+     * @return ready to use class instance
+     */
+    public static function getInstance()
+    {
+		if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+			bx_import('BxTemplLiveUpdates');
+			$GLOBALS['bxDolClasses'][__CLASS__] = new BxTemplLiveUpdates();
+		}
+
+		return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    /**
+     * 
+     * Is called via Ajax Requests only. 
+     */
+    public function perform()
+    {
+		$iIndex = $this->_getIndex();
+
+		$this->_aSystems = $this->_getCachedSystems();
+    	if(empty($this->_aSystems) || !is_array($this->_aSystems))
+    		return array();
+
+		$aCached = $this->_getCachedData();
+		$aRequested = $this->_getRequestedData($iIndex, true);
+
+		$aResult = array();
+		$bUpdateCache = false;
+		foreach($aRequested as $sName => $aData) {
+			if(isset($aCached[$sName]) && (int)$aCached[$sName] == (int)$aData['count'])
+				continue;
+
+			$aResultData = array('count_new' => $aData['count'], 'count_old' => $aCached[$sName]);
+			if(isset($aData['data']))
+				$aResultData = array_merge($aResultData, $aData['data']);
+
+			$aResult[] = array(
+				'data' => $aResultData,
+				'method' => $aData['method']
+			);
+
+			$aCached[$sName] = $aData['count'];
+			$bUpdateCache = true;
+		}
+
+		if($bUpdateCache)
+			$this->_updateCached('data', $aCached);
+
+    	return $aResult;
+    }
+
+    /**
+     * 
+     * Add transient live update for current user on this current page. 
+     * @param string $sName - unique name.
+     * @param integer $iFrequency - call frequency.
+     * @param string $sServiceCall - serialized service call.
+     * @param boolean $bActive - add active/not active live update.
+     */
+    public function add($sName, $iFrequency, $sServiceCall, $bActive = true)
+    {
+    	if(isset($this->_aSystems[$sName]))
+    		return false;
+
+    	$this->_aSystems[$sName] = array(
+    		'name' => $sName,
+    		'frequency' => $iFrequency,
+    		'service_call' => $sServiceCall,
+    		'active' => $bActive ? 1 : 0
+    	);
+
+    	$this->_updateCached('systems', $this->_aSystems);
+
+    	return true;
+    }
+
+    protected function _getIndex()
+    {
+		bx_import('BxDolSession');
+    	$oSession = BxDolSession::getInstance();
+
+		$iIndex = (int)$oSession->getValue($this->_sSessionKey);
+		$oSession->setValue($this->_sSessionKey, ($iIndex + 1));
+
+		return $iIndex;
+    }
+
+    protected function _getCacheInfo()
+    {
+    	return array(
+    		$this->_oQuery->getDbCacheObject(),
+    		$this->_oQuery->genDbCacheKey($this->_sCacheKey),
+    		$this->_iCacheTTL
+    	);
+    }
+
+    protected function _getCached($sType)
+    {
+    	list($oCache, $sCacheKey, $iCacheTtl) = $this->_getCacheInfo();
+
+    	$aCached = $oCache->getData($sCacheKey, $iCacheTtl);
+    	if(empty($aCached[$sType])) {
+    		switch($sType) {
+    			case 'systems':
+    				$aCached[$sType] = $this->_oQuery->getSystems();
+    				break;
+
+    			case 'data':
+    				$aRequested = $this->_getRequestedData();
+    				foreach($aRequested as $sName => $aData)
+    					$aCached[$sType][$sName] = $aData['count'];
+    				break;
+    		}
+
+    		if(!empty($aCached[$sType]))
+    			$oCache->setData($sCacheKey, $aCached, $iCacheTtl);
+    	}
+
+    	return $aCached[$sType];
+    }
+
+	protected function _getCachedSystems()
+    {
+    	return $this->_getCached('systems');
+    }
+
+    protected function _getCachedData()
+    {
+    	return $this->_getCached('data');
+    }
+
+	protected function _updateCached($sKey, $aData)
+    {
+    	list($oCache, $sCacheKey, $iCacheTtl) = $this->_getCacheInfo();
+
+    	$aCached = $oCache->getData($sCacheKey, $iCacheTtl);
+    	$aCached[$sKey] = $aData;
+
+    	$oCache->setData($sCacheKey, $aCached, $iCacheTtl);
+    }
+
+    protected function _getRequestedData($iIndex = 0, $bIndexCheck = false)
+    {
+    	$aResult = array();
+
+    	foreach($this->_aSystems as $aSystem) {
+    		if(empty($aSystem) || !is_array($aSystem) || (int)$aSystem['active'] != 1)
+    			continue;
+
+			if($bIndexCheck && $iIndex % (int)$aSystem['frequency'] != 0)
+				continue;
+
+			if(!BxDolService::isSerializedService($aSystem['service_call']))
+				continue;
+
+			$aResponce = BxDolService::callSerialized($aSystem['service_call']);
+			if(empty($aResponce) || !is_array($aResponce) || !isset($aResponce['count'], $aResponce['method']))
+				continue;
+
+			$aResult[$aSystem['name']] = $aResponce;
+    	}
+
+    	return $aResult;
+    }
+}
+
+/** @} */

+ 38 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolLiveUpdatesQuery.php

@@ -0,0 +1,38 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolDb');
+
+/**
+ * @see BxDolLiveUpdates
+ */
+class BxDolLiveUpdatesQuery extends BxDolDb
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+	public function getSystems()
+    {
+        if(!isset($GLOBALS['bx_dol_live_updates_systems']))
+            $GLOBALS['bx_dol_live_updates_systems'] = BxDolDb::getInstance()->fromCache('sys_objects_live_updates', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `name` AS `name`,
+                    `frequency` AS `frequency`,
+                    `service_call` AS `service_call`,
+                    `active` AS `active`
+                FROM `sys_objects_live_updates`', 'name');
+
+        return $GLOBALS['bx_dol_live_updates_systems'];
+    }
+}
+
+/** @} */

+ 280 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolMenu.php

@@ -0,0 +1,280 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolMenuQuery');
+bx_import('BxDolPermalinks');
+
+/**
+ * @page objects
+ * @section menu Menu
+ * @ref BxDolMenu
+ */
+
+/**
+ * Menus.
+ *
+ * Menu uses some set of items and the template to display it,
+ * so it is possible to have several menus which uses the same set of items but different templates.
+ *
+ * Menu is any set of some links or actions, for example menu can be links in site's footer or actions in profile view.
+ *
+ *
+ * @section menu_create Creating the Menu object:
+ *
+ * 1. Add record to 'sys_objects_menu' table:
+ *
+ * - object: name of the menu object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing; for example: bx_groups_actions - actions menu in group view.
+ * - title: name of the menu, displayed in the studio menu builder.
+ * - set_name: name of items' set.
+ * - module: the module this menu belongs to.
+ * - template_id: the template to use for menu displaying, this is id from 'sys_menu_templates' table.
+ * - deletable: it determines if menu can be deleted from the studio menu builder.
+ * - active: it is possible to disable particular menu, then it will not be displayed.
+ * - override_class_name: user defined class name which is derived from BxTemplMenu.
+ * - override_class_file: the location of the user defined class, leave it empty if class is located in system folders.
+ *
+ * Menu templates are stored in 'sys_menu_templates' table:
+ * - id: template id.
+ * - template: template file.
+ * - title: template title to display in the studio menu builder.
+ * All menu templates iterate through 'bx_repeat:menu_items' and use the following template variables for each menu item: __link__, __target__, __onclick__, __title__, __class_add__.
+ *
+ *
+ * 2. Add menu an empty menu set to 'sys_menu_sets' table (if you want to use new set of items for created menu):
+ * - set_name: the set name.
+ * - module: the module this set belongs to.
+ * - title: name of the set, displayed in studio menu builder.
+ * - deletable: it determines if the set can be deleted from menu builder.
+ *
+ *
+ * 3. Add menu items to the set by adding records to 'sys_menu_items' table:
+ * - set_name: the set name this item belogs to.
+ * - module: the module this item belongs to.
+ * - name: name of the item (not displayed to the end user), unique in the particular set.
+ * - title: menu item title to display to the end user, please note that some templates can still display menu as icons without text titles.
+ * - link: menu item URL.
+ * - onclick: menu item onclick event.
+ * - target: menu item target.
+ * - icon: menu item icon, please note that some templates can still display menu as text without icons.
+ * - addon: display additional data near menu item, only for supported menu templates, this is serialized array of service call parameters: module - module name, method - service method name, params - array of parameters.
+ * - visible_for_levels: bit field with set of member level ids. To use member level id in bit field - the level id minus 1 as power of 2 is used, for example:
+ *      - user level id = 1 -> 2^(1-1) = 1
+ *      - user level id = 2 -> 2^(2-1) = 2
+ *      - user level id = 3 -> 2^(3-1) = 4
+ *      - user level id = 4 -> 2^(4-1) = 8
+ * - active: it is possible to disable particular menu item, then it will not be displayed.
+ * - order: menu item order in the particular set.
+ *
+ *
+ * 4. Display Menu.
+ * Use the following sample code to display menu:
+ * @code
+ *     bx_import('BxTemplMenu');
+ *     $oMenu = BxTemplMenu::getObjectInstance('sample_menu'); // 'sample_menu' is 'object' field from 'sys_objects_menu' table.
+ *     if ($oMenu)
+ *         echo $oMenu->getCode; // display menu
+ * @endcode
+ *
+ * But in most cases you don't need to use above code to display menu,
+ * menu objects are integrated into pages - there is special 'menu' page block type for it.
+ *
+ */
+class BxDolMenu extends BxDol implements iBxDolFactoryObject, iBxDolReplaceable
+{
+    protected static $SEL_MODULE = '';
+    protected static $SEL_NAME = '';
+
+    protected $_sSelModule;
+    protected $_sSelName;
+
+    protected $_sObject;
+    protected $_aObject;
+    protected $_oQuery;
+    protected $_oPermalinks;
+    protected $_aMarkers = array ();
+
+    /**
+     * Constructor
+     * @param $aObject array of menu options
+     */
+    public function __construct($aObject)
+    {
+        parent::__construct();
+
+        $this->_sObject = isset($aObject['object']) ? $aObject['object'] : 'bx-menu-obj-' . time() . rand(0, PHP_INT_MAX);
+        $this->_aObject = $aObject;
+        $this->_oQuery = new BxDolMenuQuery($this->_aObject);
+        $this->_oPermalinks = BxDolPermalinks::getInstance();
+
+        if (isLogged()) {
+            bx_import('BxDolProfile');
+            $oProfile = BxDolProfile::getInstance();
+            if ($oProfile) {
+                $this->addMarkers(array(
+                    'member_id' => $oProfile->id(),
+                    'member_display_name' => $oProfile->getDisplayName(),
+                    'member_url' => $oProfile->getUrl(),
+                ));
+            }
+        }
+    }
+
+    /**
+     * Get menu object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject)
+    {
+        if (isset($GLOBALS['bxDolClasses']['BxDolMenu!'.$sObject]))
+            return $GLOBALS['bxDolClasses']['BxDolMenu!'.$sObject];
+
+        $aObject = BxDolMenuQuery::getMenuObject($sObject);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        bx_import('BxTemplMenu');
+        $sClass = 'BxTemplMenu';
+        if (!empty($aObject['override_class_name'])) {
+            $sClass = $aObject['override_class_name'];
+            if (!empty($aObject['override_class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+            else
+                bx_import($sClass);
+        }
+
+        $o = new $sClass($aObject);
+
+        return ($GLOBALS['bxDolClasses']['BxDolMenu!'.$sObject] = $o);
+    }
+
+    /**
+     * Set selected menu item globally.
+     * @param $sModule menu item module to set as selected
+     * @param $sName menu item name to set as selected
+     */
+    static public function setSelectedGlobal ($sModule, $sName)
+    {
+        self::$SEL_MODULE = $sModule;
+        self::$SEL_NAME = $sName;
+    }
+
+    /**
+     * Process menu triggers.
+     * Menu triggers allow to automatically add menu items to modules with no different if dependant module was install before or after the module menu item belongs to.
+     * For example module "Notes" adds menu items to all profiles modules (Persons, Organizations, etc)
+     * with no difference if persons module was installed before or after "Notes" module was installed.
+     * @param $sSetName set name to ad menu item to
+     * @param $aMenuItem array of menu item descrition, array keys are database fields names, array values are database fields values
+     * @return true on success
+     */
+    static public function processMenuTrigger ($sMenuTriggerName)
+    {
+        // get list of active modules
+        bx_import('BxDolModuleQuery');
+        $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array(
+            'type' => 'modules',
+            'active' => 1,
+        ));
+
+        // get list of menu triggers
+        $aMenuItem = BxDolMenuQuery::getMenuTriggers($sMenuTriggerName);
+
+        // check each menu item trigger for all modules
+        foreach ($aMenuItem as $aMenuItem) {
+            foreach ($aModules as $aModule) {
+                if (!BxDolRequest::serviceExists($aModule['name'], 'get_menu_set_name_for_menu_trigger'))
+                    continue;
+
+                if (!($sMenuSet = BxDolService::call($aModule['name'], 'get_menu_set_name_for_menu_trigger', array($sMenuTriggerName))))
+                    continue;
+
+                $aMenuItem['set_name'] = $sMenuSet;
+                BxDolMenuQuery::addMenuItemToSet($aMenuItem);
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Set selected menu item for current menu object only.
+     * @param $sModule menu item module to set as selected
+     * @param $sName menu item name to set as selected
+     */
+    public function setSelected ($sModule, $sName)
+    {
+        $this->_sSelModule = $sModule;
+        $this->_sSelName = $sName;
+    }
+
+    /**
+     * Add replace markers.
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Remove marker
+     * @param $s marker key
+     */
+    public function removeMarker ($s) 
+    {
+        unset($this->_aMarkers[$s]);
+    }
+
+    /**
+     * Check if menu items is selected.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isSelected ($a)
+    {
+        if ($this->_sSelModule || $this->_sSelName)
+            return (!isset($a['module']) || $a['module'] == $this->_sSelModule) && (isset($a['name']) && $a['name'] == $this->_sSelName) ? true : false;
+        return (!isset($a['module']) || $a['module'] == self::$SEL_MODULE) && (isset($a['name']) && $a['name'] == self::$SEL_NAME) ? true : false;
+    }
+
+    /**
+     * Check if menu items is visible.
+     * @param $a menu item array
+     * @return boolean
+     */
+    protected function _isVisible ($a)
+    {
+        bx_import('BxDolAcl');
+        return BxDolAcl::getInstance()->isMemberLevelInSet($a['visible_for_levels']);
+    }
+
+    /**
+     * Replace provided markers in menu item array, curently markers are replaced in title, link and onclick fields.
+     * @param $a menu item array
+     * @return array where markes are replaced with real values
+     */
+    protected function _replaceMarkers ($a)
+    {
+        if (empty($this->_aMarkers))
+            return $a;
+        $aReplacebleFields = array ('title', 'link', 'onclick');
+        foreach ($aReplacebleFields as $sField)
+            if (isset($a[$sField]))
+                $a[$sField] = bx_replace_markers($a[$sField], $this->_aMarkers);
+        return $a;
+    }
+
+}
+
+/** @} */

+ 364 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolMetatags.php

@@ -0,0 +1,364 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolMetatagsQuery');
+
+define('BX_METATAGS_KEYWORDS_MAX', 9);
+
+/**
+ * @page objects
+ * @section metatags Meta-tags
+ * @ref BxDolMetatags
+ */
+
+// TODO: client side controls: new form input for getting user's location, some js to help to enter @mentions and #keywords(optional)
+// TODO: implementation with some modules
+// TODO: integrate to comments, try to integrate to one metatags object - maybe specify metatags object in comments object and make appropriate changes in comments, comments content should be treated as main conetent and main content should be shown in search results)
+// TODO: pages for listing content by @keyword, @mention, country location, city location, or BETTER try integrate it to the search engine, to not make separate pages in each module
+// TODO: integration with notifications, when smbd is @mentioned
+
+/**
+ * Meta-tags for different content.
+ */
+class BxDolMetatags extends BxDol implements iBxDolFactoryObject
+{
+    protected $_sObject;
+    protected $_aObject;
+    protected $_oQuery;
+    protected $_aMetas = array ();
+
+    /**
+     * Constructor
+     * @param $aObject array of metags object options
+     */
+    public function __construct($aObject)
+    {
+        parent::__construct();
+
+        $this->_sObject = $aObject['object'];
+        $this->_aObject = $aObject;
+        
+        $a = array ('keywords', 'locations', 'mentions', 'pictures');
+        foreach ($a as $sMeta) {
+            if (empty($this->_aObject['table_' . $sMeta]))
+                continue;
+            $this->_aMetas[] = $sMeta;
+        }
+
+        $this->_oQuery = new BxDolMetatagsQuery($this->_aObject);
+    }
+
+    /**
+     * Get metatags object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject)
+    {
+        if (isset($GLOBALS['bxDolClasses']['BxDolMetatags!'.$sObject])) 
+            return $GLOBALS['bxDolClasses']['BxDolMetatags!'.$sObject];
+
+        $aObject = BxDolMetatagsQuery::getMetatagsObject($sObject);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        $sClass = $aObject['override_class_name'] ? $aObject['override_class_name'] : 'BxDolMetatags';
+        if (!empty($aObject['override_class_file']))
+            require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+        else
+            bx_import($sClass);
+
+        $o = new $sClass($aObject);
+
+        return ($GLOBALS['bxDolClasses']['BxDolMetatags!'.$sObject] = $o);
+    }
+
+    /**
+     * Add all available meta tags to the head section 
+     * @return number of successfully added metas
+     */
+    public function metaAdd($iId, $mixedImage = false)
+    {
+        $i = 0;
+        foreach ($this->_aMetas as $sMeta) {
+            $sFunc = $sMeta . 'AddMeta';
+            $i += $this->$sFunc($iId);
+        }
+
+        if ($mixedImage && is_array($mixedImage)) {
+            bx_import('BxDolStorage');
+            $oStorage = BxDolStorage::getObjectInstance($mixedImage['object']);
+            $mixedImage = $oStorage ? $oStorage->getFileUrlById($mixedImage['id']) : false;
+        }
+        if ($mixedImage) 
+            BxDolTemplate::getInstance()->addPageMetaImage($mixedImage);
+
+        return $i;
+    }
+
+
+
+    /**
+     * Add #keywords from the string
+     * @param $iId content id
+     * @param $s string with #keywords
+     * @return number of found keywords
+     */
+    public function keywordsAdd($iId, $s) 
+    {
+        $a = array();
+        if (!preg_match_all('/(\#[\pL\pN]+)/u', strip_tags($s), $a)) {
+            $this->_oQuery->keywordsDelete($iId);
+            return 0;
+        }
+
+        $a[0] = array_unique($a[0]);
+
+        return $this->_oQuery->keywordsAdd($iId, array_slice($a[0], 0, BX_METATAGS_KEYWORDS_MAX));
+    }
+
+    /**
+     * Add links to the #keywords in the string
+     * @param $iId content id
+     * @param $s string with #keywords
+     * @return modified string where all #keywords are transformed to links with rel="tag" attribute
+     */
+    public function keywordsParse($iId, $s) 
+    {   
+        $a = $this->keywordsGet($iId);
+        if (empty($a))
+            return $s;
+    
+        foreach ($a as $sKeyword)
+            $s = str_ireplace('#' . $sKeyword, '<a rel="tag" href="' . BX_DOL_URL_ROOT . 'searchKeyword.php?type=keyword&keyword=' . rawurlencode($sKeyword) . '"><s>#</s><b>' . $sKeyword . '</b></a>', $s);
+
+        return $s;
+    }
+
+    /**
+     * Add keywords meta info to the head section
+     * @param $iId content id
+     */
+    protected function keywordsAddMeta($iId)
+    {
+        BxDolTemplate::getInstance()->addPageKeywords($this->keywordsGet($iId));
+    }
+
+    /**
+     * Get list of keywords associated with the content
+     * @return array of keywords
+     */
+    public function keywordsGet($iId)
+    {
+        return $this->_oQuery->keywordsGet($iId);
+    }
+
+    /**
+     * Set condition for search results object for meta keyword
+     * @param $oSearchResult search results object
+     * @param $sKeyword keyword
+     */
+    public function keywordsSetSearchCondition($oSearchResult, $sKeyword)
+    {
+        $oSearchResult->aCurrent['restriction']['meta_keyword'] = array(
+            'value' => $sKeyword,
+            'field' => 'keyword',
+            'operator' => '=',
+            'table' => $this->_aObject['table_keywords'],
+        );
+
+        $oSearchResult->aCurrent['join']['meta_keyword'] = array(
+            'type' => 'INNER',
+            'table' => $this->_aObject['table_keywords'],
+            'mainField' => $oSearchResult->aCurrent['ident'],
+            'onField' => 'object_id',
+            'joinFields' => array(),
+        );
+    }
+
+
+
+    /**
+     * Add location for the content
+     * @param $iId content id
+     * @param $sLatitude latitude
+     * @param $sLongitude longitude
+     * @param $sCountryCode optional 2 letters country code (ISO 3166-1)
+     * @param $sState optional state/province/territory name
+     * @param $sCity optional city name
+     * @param $sZip optional ZIP/postcode
+     * @return true if location was added, or false otherwise
+     */
+    public function locationsAdd($iId, $sLatitude, $sLongitude, $sCountryCode, $sState, $sCity, $sZip = '') 
+    {
+        return $this->_oQuery->locationsAdd($iId, $sLatitude, $sLongitude, $sCountryCode, $sState, $sCity, $sZip);
+    }
+    public function locationsAddFromForm($iId, $sName, $oForm = null) 
+    {
+        if (!$oForm)
+            $oForm = new BxDolForm(array(), false);
+        $this->locationsAdd($iId, $oForm->getCleanValue($sName.'_lat'), $oForm->getCleanValue($sName.'_lng'), $oForm->getCleanValue($sName.'_country'), $oForm->getCleanValue($sName.'_state'), $oForm->getCleanValue($sName.'_city'), $oForm->getCleanValue($sName.'_zip'));
+    }
+
+    /**
+     * Get locations string with links
+     * @param $iId content id
+     * @return string with links to country and city
+     */
+    public function locationsString($iId)
+    {
+        bx_import('BxDolForm');
+        bx_import('BxDolFormQuery');
+        $aCountries = BxDolFormQuery::getDataItems('Country');
+        $aLocation = $this->locationGet($iId);
+        if (!$aLocation || !$aLocation['country'] || !isset($aCountries[$aLocation['country']]))
+            return '';
+
+        $sCountryUrl = '<a href="' . BX_DOL_URL_ROOT . 'searchKeyword.php?type=location_country&keyword=' . $aLocation['country'] . '">' . $aCountries[$aLocation['country']] . '</a>';
+        if (!$aLocation['city'])
+            return _t('_sys_location_country', $sCountryUrl);
+
+        $sCityUrl = '<a href="' . BX_DOL_URL_ROOT . 'searchKeyword.php?type=location_country_city&keyword=' . $aLocation['country'] . '&state=' . rawurlencode($aLocation['state']) . '&city=' . rawurlencode($aLocation['city']) . '">' . $aLocation['city'] . '</a>';
+            
+        return _t('_sys_location_country_city', $sCountryUrl, $sCityUrl);
+    }
+
+    /**
+     * Add keywords meta info to the head section
+     * @param $iId content id
+     */
+    protected function locationsAddMeta($iId) 
+    {
+        $aLocation = $this->locationGet($iId);
+        if (!empty($aLocation['lat']) && !empty($aLocation['lng']) && !empty($aLocation['country']))
+            BxDolTemplate::getInstance()->addPageMetaLocation($aLocation['lat'], $aLocation['lng'], $aLocation['country']);
+    }
+
+    /**
+     * Set condition for search results object for meta locations
+     * @param $oSearchResult search results object
+     * @param $sCountry country and other location info
+     */
+    public function locationsSetSearchCondition($oSearchResult, $sCountry, $sState = false, $sCity = false, $sZip = false)
+    {
+        $a = array('country' => 'sCountry', 'state' => 'sState', 'city' => 'sCity', 'zip' => 'sZip');
+        foreach ($a as $sIndex => $sVar) {
+            if (!$$sVar)
+                continue;
+
+            $oSearchResult->aCurrent['restriction']['meta_location_' . $sIndex] = array(
+                'value' => $$sVar,
+                'field' => $sIndex,
+                'operator' => '=',
+                'table' => $this->_aObject['table_locations'],
+            );
+        }
+
+        $oSearchResult->aCurrent['join']['meta_keyword'] = array(
+            'type' => 'INNER',
+            'table' => $this->_aObject['table_locations'],
+            'mainField' => $oSearchResult->aCurrent['ident'],
+            'onField' => 'object_id',
+            'joinFields' => array(),
+        );
+    }
+
+    /**
+     * Get location
+     * @return location array
+     */
+    public function locationGet($iId, $sPrefix = '')
+    {
+        $a = $this->_oQuery->locationGet($iId);
+        if (!$sPrefix)
+            return $a;
+
+        $aRet = array();
+        foreach ($a as $sKey => $sVal)
+            $aRet[$sPrefix . '_' . $sKey] = $sVal;
+        return $aRet;
+    }
+
+    /**
+     * Add @mentions from the string (most probably @mentions will be some sort of links already, so parsing may have to look for smth like <a data-mention="bx_persons|123">mention name</a> instead of @mention, since there is no usernames for profiles modules and name could contain spaces and othr characters)
+     * @param $iId content id
+     * @param $s string with @mentions
+     * @return number of found mentions 
+     */
+    public function mentionsAdd($iId, $s) 
+    {
+        // TODO:        
+    }
+
+    /**
+     * Add links to the @mentions in the string (actual tranformation may have to be performed with ready links like <a data-mention="bx_persons|123">mention name</a>)
+     * @param $iId content id
+     * @param $s string with @mentions
+     * @return modified string where all @mentions are transformed to links
+     */
+    public function mentionsParse($iId, $s) 
+    {
+        // TODO:
+    }
+
+    /**
+     * No mentions meta info in the head section
+     */
+    protected function mentionsAddMeta($iId) 
+    {
+        return 0;
+    }
+
+    /**
+     * Set condition for search results object for mentions
+     * @param $oSearchResult search results object
+     * @param $sMention smbd
+     */
+    public function mentionsSetSearchCondition($oSearchResult, $sMention)
+    {
+        // TODO:
+    }
+
+
+
+    /**
+     * Add thumb for the content
+     * @param $iId content id
+     * @param $sStorageObject storage object name where file is stored
+     * @param $mixedFileId file id in the storage
+     * @return true if file was added, or false otherwise
+     */
+    public function picturesAdd($iId, $sStorageObject, $mixedFileId) 
+    {
+        // TODO:        
+    }
+
+    /**
+     * Add thumb meta info to the head section
+     * @param $iId content id
+     */
+    protected function picturesAddMeta($iId) 
+    {
+        // TODO:
+    }
+
+
+
+    /**
+     * Delete all data associated with the content
+     * @param $iId content id
+     */
+    public function onDeleteContent($iId) 
+    {
+        // TODO: 
+    }
+
+}
+
+/** @} */

+ 85 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolObject.php

@@ -0,0 +1,85 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+/**
+ * Base class for all Dolphin Object classes.
+ * Child classes usually represents high level programming constructions to generate ready 'objects' functionality, like Comments, Votings, Forms.
+ */
+class BxDolObject extends BxDol
+{
+    protected $_iId = 0; ///< item id the action to be performed with
+    protected $_sSystem = ''; ///< current system name
+    protected $_aSystem = array(); ///< current system array
+
+    protected $_oQuery = null;
+
+    public function __construct($sSystem, $iId, $iInit = 1)
+    {
+        parent::__construct();
+
+        $aSystems = $this->getSystems();
+        if(!isset($aSystems[$sSystem]))
+            return;
+
+        $this->_sSystem = $sSystem;
+        $this->_aSystem = $aSystems[$sSystem];
+
+        if(!$this->isEnabled())
+            return;
+
+        if($iInit)
+            $this->init($iId);
+    }
+
+    public function init($iId)
+    {
+        if(!$this->isEnabled())
+            return false;
+
+        if(empty($this->_iId) && $iId)
+            $this->setId($iId);
+
+        return true;
+    }
+
+    public function getSystemId()
+    {
+        return $this->_aSystem['id'];
+    }
+
+    public function getSystemName()
+    {
+        return $this->_sSystem;
+    }
+
+    public function getSystemInfo()
+    {
+        return $this->_aSystem;
+    }
+
+    public function getId()
+    {
+        return $this->_iId;
+    }
+
+    public function setId($iId)
+    {
+        if($iId == $this->getId())
+            return;
+
+        $this->_iId = $iId;
+    }
+
+    public function isEnabled ()
+    {
+        return $this->_aSystem && (int)$this->_aSystem['is_on'] == 1;
+    }
+}
+
+/** @} */

+ 423 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolProfile.php

@@ -0,0 +1,423 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolProfileQuery');
+
+define('BX_PROFILE_ACTION_AUTO', 0); ///< automatic action without any checking
+define('BX_PROFILE_ACTION_MANUAL', 1); ///< manual action performed by human
+define('BX_PROFILE_ACTION_ROBOT', 2); ///< action peformed by some robot based on some conditions
+
+class BxDolProfile extends BxDol implements iBxDolProfile
+{
+    protected $_iProfileID;
+    protected $_oQuery;
+
+    /**
+     * Constructor
+     */
+    protected function __construct ($iProfileId)
+    {
+        $iProfileId = (int)$iProfileId;
+        $sClass = get_class($this) . '_' . $iProfileId;
+        if (isset($GLOBALS['bxDolClasses'][$sClass]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->_iProfileID = $iProfileId; // since constructor is protected $iProfileId is always valid
+        $this->_oQuery = BxDolProfileQuery::getInstance();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        $sClass = get_class($this) . '_' . $this->_iProfileID;
+        if (isset($GLOBALS['bxDolClasses'][$sClass]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of Account Profile by account id
+     */
+    public static function getInstanceAccountProfile($iAccountId = false)
+    {
+        if (!$iAccountId)
+            $iAccountId = getLoggedId();
+        $oQuery = BxDolProfileQuery::getInstance();
+        $aProfile = $oQuery->getProfileByContentTypeAccount($iAccountId, 'system', $iAccountId);
+        if (!$aProfile)
+            return false;
+        return self::getInstance($aProfile['id']);
+    }
+
+    /**
+     * Get singleton instance of Profile by account id, content id and type
+     */
+    public static function getInstanceByContentTypeAccount($iContent, $sType, $iAccountId = false)
+    {
+        if (!$iAccountId)
+            $iAccountId = getLoggedId();
+        $oQuery = BxDolProfileQuery::getInstance();
+        $aProfile = $oQuery->getProfileByContentTypeAccount($iContent, $sType, $iAccountId);
+        if (!$aProfile)
+            return false;
+        return self::getInstance($aProfile['id']);
+    }
+
+    /**
+     * Get singleton instance of Profile by content id and type
+     */
+    public static function getInstanceByContentAndType($iContent, $sType)
+    {
+        $oQuery = BxDolProfileQuery::getInstance();
+        $aProfile = $oQuery->getProfileByContentAndType($iContent, $sType);
+        if (!$aProfile)
+            return false;
+        return self::getInstance($aProfile['id']);
+    }
+
+    /**
+     * Get singleton instance of Profile by profile id
+     */
+    public static function getInstance($mixedProfileId = false)
+    {
+        if (!$mixedProfileId) {
+            $oQuery = BxDolProfileQuery::getInstance();
+            $mixedProfileId = $oQuery->getCurrentProfileByAccount(getLoggedId());
+        }
+
+        $iProfileId = self::getID($mixedProfileId);
+        if (!$iProfileId)
+            return false;
+
+        $sClass = __CLASS__ . '_' . $iProfileId;
+        if (!isset($GLOBALS['bxDolClasses'][$sClass]))
+            $GLOBALS['bxDolClasses'][$sClass] = new BxDolProfile($iProfileId);
+
+        return $GLOBALS['bxDolClasses'][$sClass];
+    }
+
+    /**
+     * Get profile id
+     */
+    public function id()
+    {
+        return $this->_oQuery->getIdById($this->_iProfileID);
+    }
+
+    /**
+     * Get account id associated with the profile
+     */
+    public function getAccountId()
+    {
+        $aInfo = $this->getInfo();
+        return $aInfo['account_id'];
+    }
+
+    /**
+     * Get account object associated with the profile
+     */
+    public function getAccountObject()
+    {
+        bx_import('BxDolAccount');
+        return BxDolAccount::getInstance($this->getAccountId());
+    }
+
+    /**
+     * Get content id associated with the profile
+     */
+    public function getContentId()
+    {
+        $aInfo = $this->getInfo();
+        return $aInfo['content_id'];
+    }
+
+    /**
+     * Check if profile status is active
+     */
+    public function isActive($iProfileId = false)
+    {
+        return BX_PROFILE_STATUS_ACTIVE == $this->getStatus($iProfileId);
+    }
+
+    /**
+     * Get profile status
+     */
+    public function getStatus($iProfileId = false)
+    {
+        $aInfo = $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+        return $aInfo['status'];
+    }
+
+    /**
+     * Get profile info
+     */
+    public function getInfo($iProfileId = 0)
+    {
+        return $this->_oQuery->getInfoById((int)$iProfileId ? $iProfileId : $this->_iProfileID);
+    }
+
+    /**
+     * Validate profile id.
+     * @param $s - profile id
+     * @return profile id or false if profile was not found
+     */
+    static public function getID($s)
+    {
+        $iId = BxDolProfileQuery::getInstance()->getIdById((int)$s);
+        return $iId ? $iId : false;
+    }
+
+    /**
+     * Get name to display in thumbnail
+     */
+    public function getDisplayName($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_name', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get profile url
+     */
+    public function getUrl($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_url', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get profile unit
+     */
+    public function getUnit($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_unit', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get thumbnail url
+     */
+    public function getAvatar($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_avatar', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get thumbnail url
+     */
+    public function getThumb($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_thumb', array($aInfo['content_id']));
+    }
+
+    /**
+     * Get icon url
+     */
+    public function getIcon($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_icon', array($aInfo['content_id']));
+    }
+
+    /**
+     * get profile edit page url
+     */
+    public function getEditUrl($iProfileId = 0)
+    {
+        $aInfo = $this->getInfo($iProfileId);
+        return BxDolService::call($aInfo['type'], 'profile_edit_url', array($aInfo['content_id']));
+    }
+
+    /**
+     * Delete profile.
+     * @param $ID - optional profile id to delete
+     * @param $bForceDelete - force deletetion is case of account profile deletion
+     * @return false on error, or true on success
+     */
+    function delete($ID = false, $bForceDelete = false)
+    {
+        $ID = (int)$ID;
+        if (!$ID)
+            $ID = $this->_iProfileID;
+
+        $aProfileInfo = $this->_oQuery->getInfoById($ID);
+        if (!$aProfileInfo)
+            return false;
+
+        // delete system profiles (accounts) is not allowed, instead - delete whole account
+        if (!$bForceDelete && 'system' == $aProfileInfo['type'])
+            return false;
+
+        // switch profile context if deleted profile is active profile context
+        bx_import('BxDolAccount');
+        $oAccount = BxDolAccount::getInstance ($aProfileInfo['account_id']);
+        $aAccountInfo = $oAccount->getInfo();
+        if (!$bForceDelete && $ID == $aAccountInfo['profile_id']) {
+            $oProfileAccount = BxDolProfile::getInstanceAccountProfile($aProfileInfo['account_id']);
+            $oAccount->updateProfileContext($oProfileAccount->id());
+        }
+
+        // create system event before deletion
+        $isStopDeletion = false;
+        bx_alert('profile', 'before_delete', $ID, 0, array('stop_deletion' => &$isStopDeletion));
+        if ($isStopDeletion)
+            return false;
+
+        // delete associated comments
+        bx_import('BxDolCmts');
+        BxDolCmts::onAuthorDelete($ID);
+
+        // delete connections
+        bx_import('BxDolConnection');
+        $oConn = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        $oConn->onDeleteInitiatorAndContent($ID);
+
+        $oConn = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        $oConn->onDeleteInitiatorAndContent($ID);
+
+        // delete profile's acl levels
+        bx_import('BxDolAcl');
+        BxDolAcl::getInstance()->onProfileDelete($ID);
+
+        // delete profile
+        if (!$this->_oQuery->delete($ID))
+            return false;
+
+        // create system event
+        bx_alert('profile', 'delete', $ID);
+
+        // unset class instance to prevent creating the instance again
+        $this->_iProfileID = 0;
+        $sClass = get_class($this) . '_' . $ID;
+        unset($GLOBALS['bxDolClasses'][$sClass]);
+
+        return true;
+    }
+
+    /**
+     * Insert account and content id association. Also if currect profile id is not defined - it updates current profile id in account.
+     * @param $iAccountId account id
+     * @param $iContentId content id
+     * @param $sStatus profile status
+     * @param $sType profile content type
+     * @return inserted profile's id
+     */
+    static public function add ($iAction, $iAccountId, $iContentId, $sStatus, $sType = 'system')
+    {
+        $oQuery = BxDolProfileQuery::getInstance();
+        if (!($iProfileId = $oQuery->insertProfile ($iAccountId, $iContentId, $sStatus, $sType)))
+            return false;
+        bx_alert('profile', 'add', $iProfileId, 0, array('module' => $sType, 'content' => $iContentId, 'account' => $iAccountId, 'status' => $sStatus, 'action' => $iAction));
+        return $iProfileId;
+    }
+
+    /**
+     * Change profile status to 'Active'
+     */
+    public function activate($iAction, $iProfileId = 0)
+    {
+        $sStatus = $this->getStatus($iProfileId);
+        return $this->changeStatus(BX_PROFILE_STATUS_ACTIVE, BX_PROFILE_STATUS_PENDING == $sStatus ? 'approve' : 'activate', $iAction, $iProfileId);
+    }
+
+    /**
+     * Change profile status from 'Pending' to the next level - 'Active'
+     */
+    public function approve($iAction, $iProfileId = 0)
+    {
+        return $this->changeStatus(BX_PROFILE_STATUS_ACTIVE, 'approve', $iAction, $iProfileId);
+    }
+
+    /**
+     * Change profile status to 'Pending'
+     */
+    public function disapprove($iAction, $iProfileId = 0)
+    {
+        return $this->changeStatus(BX_PROFILE_STATUS_PENDING, 'disapprove', $iAction, $iProfileId);
+    }
+
+    /**
+     * Change profile status to 'Suspended'
+     */
+    public function suspend($iAction, $iProfileId = 0)
+    {
+        return $this->changeStatus(BX_PROFILE_STATUS_SUSPENDED, 'suspend', $iAction, $iProfileId);
+    }
+
+    protected function changeStatus($sStatus, $sAlertActionName, $iAction, $iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileID;
+
+        // get account and profile objects
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        $oAccount = $oProfile->getAccountObject();
+        if (!$oProfile || !$oAccount)
+            return false;
+
+        // change status
+        if (!$this->_oQuery->changeStatus($iProfileId, $sStatus))
+            return false;
+
+        // alert about status changing
+        bx_alert('profile', $sAlertActionName, $iProfileId, false, array('action' => $iAction));
+
+        // send email to member about status change
+        sendMailTemplate('t_ChangeStatus' . ucfirst($sStatus), $oAccount->id(), $iProfileId, array('status' => $sStatus), BX_EMAIL_SYSTEM);
+
+        return true;
+    }
+
+    /**
+     * Display informer message if it is possible to switch to this profile
+     */
+    public function checkSwitchToProfile($oTemplate = null, $iViewerAccountId = false, $iViewerProfileId = false)
+    {
+        if (false === $iViewerAccountId)
+            $iViewerAccountId = getLoggedId();
+        if (false === $iViewerProfileId)
+            $iViewerProfileId = bx_get_logged_profile_id();
+
+        if (!$iViewerAccountId || !$iViewerProfileId)
+            return;
+
+        if ($iViewerAccountId != $this->getAccountId() ||  $iViewerProfileId == $this->id())
+            return;
+
+        bx_import('BxDolInformer');
+        bx_import('BxDolPermalinks');
+        $oInformer = BxDolInformer::getInstance($oTemplate);
+        if ($oInformer)
+            $oInformer->add('sys-switch-profile-context', _t('_sys_txt_account_profile_context_change_suggestion', BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher', array('switch_to_profile' => $this->id()))), BX_INFORMER_INFO);
+
+    }
+
+	/**
+     * Add permament messages.
+     */
+    public function addInformerPermanentMessages ($oInformer)
+    {
+    	$aInfo = $this->getInfo();
+    	$aProfiles = $this->_oQuery->getProfilesByAccount($aInfo['account_id']);
+
+        if ($aInfo['type'] == 'system' && count($aProfiles) == 1) {
+            bx_import('BxDolPermalinks');
+            $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=account-profile-switcher');
+
+            $oInformer->add('sys-account-profile-system', _t('_sys_txt_account_profile_system', $sUrl), BX_INFORMER_ALERT);
+        }
+    }
+}
+
+/** @} */

+ 859 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolSearch.php

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

+ 2226 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolTemplate.php

@@ -0,0 +1,2226 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+ini_set('pcre.backtrack_limit', 1000000);
+
+define('BX_DOL_TEMPLATE_DEFAULT_CODE', 'lagoon');
+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_PAGE_DEFAULT', 0); ///< default, regular page
+define('BX_PAGE_CLEAR', 2); ///< clear page, without any headers and footers
+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
+
+/**
+ * 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. <bx_include_auto:template_name.html /> - 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. <bx_include_base:template_name.html /> - the content of the file would be inserted. File would be taken from base directory.
+ *  3. <bx_include_tmpl:template_name.html /> - the content of the file would be inserted. File would be taken from tmpl_xxx directory.
+ *  4. <bx_url_root /> - the value of BX_DOL_URL_ROOT variable will be inserted.
+ *  5. <bx_url_admin /> - the value of BX_DOL_URL_ADMIN variable will be inserted.
+ *  6. <bx_text:_language_key /> - _language_key will be translated using language file(function _t()) and inserted.
+       <bx_text_js:_language_key /> - _language_key will be translated using language file(function _t()) and inserted, use it to insert text into js string.
+       <bx_text_attribute:_language_key /> - _language_key will be translated using language file(function _t()) and inserted, use it to insert text into html attribute.
+ *  7. <bx_image_url:image_file_name /> - 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. <bx_icon_url:icon_file_name /> - the same with <bx_image_url:image_file_name />, but icons will be searched in the images/icons/ folders.
+ *  9. <bx_injection:injection_name /> - will be replaced with injections registered with the page and injection_name in the `sys_injections`/`sys_injections_admin`/ tables.
+ *  10. <bx_if:tag_name>some_HTML</bx_if:tag_name> - will be replaced with provided content if the condition is true, and with empty string otherwise.
+ *  11. <bx_repeat:cycle_name>some_HTML</bx_repeat:cycle_name> - 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 <head> tag.
+ *  2. injection_body - add ingection(attribute) in the <body> tag.
+ *  3. injection_header - add injection inside the <body> 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 <body> 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 BxDol implements iBxDolSingleton
+{
+    /**
+     * Main fields
+     */
+    protected $_sPrefix;
+    protected $_sRootPath;
+    protected $_sRootUrl;
+    protected $_sSubPath;
+    protected $_sInjectionsTable;
+    protected $_sInjectionsCache;
+    protected $_sCode;
+    protected $_sCodeKey;
+    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 $_bCssArchive;
+    protected $_sCssCachePrefix;
+
+    protected $_bJsCache;
+    protected $_bJsArchive;
+    protected $_sJsCachePrefix;
+
+    /**
+     * Less related fields
+     */
+    protected $_bLessEnable;
+    protected $_sLessCachePrefix;
+
+    /**
+     * Minify related fields
+     */
+    protected $_bMinifyEnable;
+
+    protected $aPage;
+    protected $aPageContent;
+
+    protected $_oConfigTemplate;
+
+    /**
+     * 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 = 'skin';
+
+        $sCode = getParam('template');
+        if(empty($sCode))
+            $sCode = BX_DOL_TEMPLATE_DEFAULT_CODE;
+        $this->_checkCode($sCode, false);
+
+        //--- Check selected template in COOKIE(the lowest priority) ---//
+        $sCode = !empty($_COOKIE[$this->_sCodeKey]) ? $_COOKIE[$this->_sCodeKey] : '';
+        $this->_checkCode($sCode, false);
+
+        //--- Check selected template in GET(the highest priority) ---//
+        $sCode = !empty($_GET[$this->_sCodeKey]) ? $_GET[$this->_sCodeKey] : '';
+        $this->_checkCode($sCode, true);
+
+        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])) {
+            bx_import('BxDolPermalinks');
+            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();
+
+        $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->_bCssArchive = $this->_bCssCache && $bArchive;
+        $this->_sCssCachePrefix = $this->_sCacheFilePrefix . 'css_';
+
+        $this->_bJsCache = !defined('BX_DOL_CRON_EXECUTE') && getParam('sys_template_cache_js_enable') == 'on';
+        $this->_bJsArchive = $this->_bJsCache && $bArchive;
+        $this->_sJsCachePrefix = $this->_sCacheFilePrefix . 'js_';
+
+        $this->_bLessEnable = true;
+        $this->_sLessCachePrefix = $this->_sCacheFilePrefix . 'less_';
+
+        $this->_bMinifyEnable = true;
+
+        $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__];
+    }
+
+    /**
+     * 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()
+    {
+        //--- Load page elements related static variables ---//
+        $this->aPage = array(
+            'name_index' => BX_PAGE_DEFAULT,
+            'header' => '',
+            'header_text' => '',
+            'keywords' => array(),
+            'location' => array(),
+            'description'  => '',
+            'robots' => '',
+            'css_name' => array(),
+            'css_compiled' => array(),
+            'css_system' => array(),
+            'js_name' => array(),
+            'js_compiled' => array(),
+            'js_system' => array(),
+            'js_options' => array(),
+            'js_translations' => array(),
+            'js_images' => array(),
+            'injections' => array()
+        );
+
+        //--- Load default CSS ---//
+        $this->addCssSystem(array(
+            'common.css',
+            'default.less',
+            'general.css',
+            'icons.css',
+            'colors.css',
+            'forms.css',
+            'media-desktop.css',
+            'media-tablet.css',
+            'media-phone.css',
+            'media-print.css',
+            BX_DOL_URL_PLUGINS_PUBLIC . 'marka/marka.min.css',
+        ));
+
+        //--- Load default JS ---//
+        $this->addJsSystem(array(
+            'jquery/jquery.min.js',
+            'jquery/jquery-migrate.min.js',
+            'jquery-ui/jquery.ui.position.min.js',
+            'spin.min.js',
+            'jquery.easing.js',
+            'jquery.cookie.min.js',
+            'moment-with-langs.js',
+            'functions.js',
+            'jquery.webForms.js',
+            'jquery.dolPopup.js',
+            'elementQuery.min.js',
+            'marka/marka.min.js',
+        ));
+
+        //--- Load default JS ---//
+        bx_import('BxDolLanguages');
+        $this->addJsTranslation(array(
+            '_are you sure?',
+            '_error occured',
+            '_sys_loading',
+        ));
+
+        //--- 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');
+        $this->_oConfigTemplate = BxTemplConfig::getInstance();
+    }
+
+    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 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 code.
+     *
+     * @return string template's code.
+     */
+    function getCode()
+    {
+        return $this->_sCode;
+    }
+
+    /**
+     * Check whether code is associated with active template.
+     *
+     * @param string  $sCode      template's unique URI.
+     * @param boolean $bSetCookie save code in COOKIE or not.
+     */
+    protected function _checkCode($sCode, $bSetCookie)
+    {
+        if(empty($sCode) || !preg_match('/^[A-Za-z0-9_-]+$/', $sCode))
+            return;
+
+        bx_import('BxDolModuleQuery');
+        $aModule = BxDolModuleQuery::getInstance()->getModuleByUri($sCode);
+        if(empty($aModule) || !is_array($aModule) || (int)$aModule['enabled'] != 1 || !file_exists($this->_sRootPath . 'modules/' . $aModule['path'] . 'data/template/'))
+            return;
+
+        bx_import('BxDolModuleConfig');
+        $oConfig = new BxDolModuleConfig($aModule);
+
+        $this->_sCode = $oConfig->getUri();
+        $this->_sSubPath = $oConfig->getDirectory();
+
+        if(!$bSetCookie || bx_get('preview'))
+            return;
+
+        $aUrl = parse_url(BX_DOL_URL_ROOT);
+        $sPath = isset($aUrl['path']) && !empty($aUrl['path']) ? $aUrl['path'] : '/';
+
+        setcookie( $this->_sCodeKey, $this->_sCode, time() + 60*60*24*365, $sPath);
+    }
+
+    /**
+     * 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));
+    }
+
+    /**
+     * 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_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @param string $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/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_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @param  string   $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @return location key. Is needed to remove the location.
+     */
+    function addDynamicLocation($sLocationPath, $sLocationUrl)
+    {
+        $sLocationKey = time();
+        $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]);
+    }
+    /**
+     * 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_dolphin]/modules/[vendor_name]/[module_name]/js/'
+     * @param string $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/js/'
+     */
+    function addLocationJs($sKey, $sLocationPath, $sLocationUrl)
+    {
+        $this->_aLocationsJs[$sKey] = array(
+            'path' => $sLocationPath,
+            'url' => $sLocationUrl
+        );
+    }
+    /**
+     * Add dynamic JS location.
+     *
+     * @param  string   $sLocationPath - location's path. For modules: '[path_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @param  string   $sLocationUrl  - location's url. For modules: '[url_to_dolphin]/modules/[vendor_name]/[module_name]/'
+     * @return location key. Is needed to remove the location.
+     */
+    function addDynamicLocationJs($sLocationPath, $sLocationUrl)
+    {
+        $sLocationKey = time();
+        $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)
+    {
+        if(is_string($mixedKey))
+            $mixedKey = array($mixedKey);
+
+        foreach($mixedKey as $sKey)
+            $this->aPage['js_translations'][$sKey] = _t($sKey, '{0}', '{1}');
+    }
+    /**
+     * 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;
+        }
+    }
+    /**
+     * 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;
+    }
+    /**
+     * 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'])) . '" />';
+
+        if (!empty($this->aPage['description']) && is_string($this->aPage['description']))
+            $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']) . '" />';
+
+        if (!empty($this->aPage['image'])) {
+            $sRet .= '<meta property="og:image" content="' . $this->aPage['image'] . '" />';
+        } else {
+            bx_import('BxTemplFunctions');
+            $sRet .= BxTemplFunctions::getInstance()->getMetaIcons();
+        }
+
+        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.
+     */
+    function getTemplate($sName)
+    {
+        return $this->_aTemplates[$sName];
+    }
+    /**
+     * 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 menu.
+     * @param $s menu object name
+     * @return html or empty string
+     */
+    function getMenu ($s)
+    {
+        bx_import('BxDolMenu');
+        $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))
+            $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 />') !== false) {
+            if (!empty($this->aPage['css_name']))
+                $this->addCss($this->aPage['css_name']);
+            $sContent = str_replace('<bx_include_css />', $this->includeFiles('css', true) . $this->includeFiles('css'), $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', true) . $this->includeFiles('js'), $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 'popup_loading':
+                bx_import('BxTemplFunctions');
+                $s = $this->parsePageByName('popup_loading.html', array());
+                $sRet = BxTemplFunctions::getInstance()->transBox('bx-popup-loading', $s, true);
+
+                bx_import('BxTemplSearch');
+                $oSearch = new BxTemplSearch();
+                $oSearch->setLiveSearch(true);
+                $sRet .= $this->parsePageByName('search.html', array(
+                    'search_form' => $oSearch->getForm(BX_DB_CONTENT_ONLY),
+                    'results' => $oSearch->getResultsContainer(),
+                ));
+
+                $sRet .= $this->getMenu ('sys_site');                
+                $sRet .= isLogged() ? $this->getMenu ('sys_add_content') : '';
+                $sRet .= isLogged() ? $this->getMenu ('sys_account_popup') : '';
+                break;
+            case 'lang':
+                $sRet = bx_lang_name();
+                break;
+            case 'main_logo':
+                bx_import('BxTemplFunctions');
+                $sRet = BxTemplFunctions::getInstance()->getMainLogo();
+                break;
+            case 'informer':
+                bx_import('BxDolInformer');
+                $oInformer = BxDolInformer::getInstance($this);
+                $sRet = $oInformer ? $oInformer->display() : '';
+                break;
+            case 'dol_images':
+                $sRet = $this->_processJsImages();
+                break;
+            case 'dol_lang':
+                $sRet = $this->_processJsTranslations();
+                break;
+            case 'dol_options':
+                $sRet = $this->_processJsOptions();
+                break;
+            case 'bottom_text':
+                $sRet = _t( '_bottom_text', date('Y') );
+                break;
+            case 'copyright':
+                $sRet = _t( '_copyright',   date('Y') ) . getVersionComment();
+                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;
+
+            default:
+                bx_import('BxTemplFunctions');
+                $sRet = ($sTemplAdd = BxTemplFunctions::getInstance()->TemplPageAddComponent($sKey)) !== false ? $sTemplAdd : $aKeyWrappers['left'] . $sKey . $aKeyWrappers['right'];
+        }
+
+        if($bProcessInjection)
+            $sRet = $this->processInjection($this->getPageNameIndex(), $sKey, $sRet);
+
+        return $sRet;
+    }
+    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);
+    }
+
+    /**
+     * 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, '/', $sPath);
+
+        $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
+        );
+    }
+    /**
+     * 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);
+    }
+
+    /**
+     * 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 = '';
+
+            $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, '/', $sPath);
+
+        $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;
+
+	            $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/lessc.inc.php');
+        $oLess = new lessc();
+        $oLess->setVariables($this->_oConfigTemplate->aLessConfig);
+
+        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;
+
+            $sPathRoot = realpath(BX_DIRECTORY_PATH_ROOT);
+            $sFile = $this->_sLessCachePrefix . trim(str_replace(array('.' . $aInfoFile['extension'], DIRECTORY_SEPARATOR), array('', '_'), bx_ltrim_str($sPathFile, $sPathRoot)), '_') . '.css';
+
+            $oLess->checkedCompile($mixed['path'], $this->_sCachePublicFolderPath . $sFile);
+            return array('url' => $this->_sCachePublicFolderUrl . $sFile, 'path' => $this->_sCachePublicFolderPath . $sFile);
+        }
+
+        return $oLess->compile($mixed);
+    }
+
+    /**
+     * Minify CSS
+     *
+     * @param  string $s CSS string to minify
+     * @return string minified CSS string.
+     */
+    function _minifyCss($s)
+    {
+        require_once(BX_DIRECTORY_PATH_PLUGINS . 'minify/lib/Minify/CSS/Compressor.php');
+        return Minify_CSS_Compressor::process($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 "<link rel=\"stylesheet\" type=\"text/css\">" . $sCode . "</link>";
+    }
+    /**
+     * 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)
+                $sResult .= $sContent;
+        }
+
+        if ($this->_bMinifyEnable && method_exists($this, $sMethodMinify))
+            $sResult = $this->$sMethodMinify($sResult);
+
+        $mixedWriteResult = false;
+        if(!empty($sResult) && ($rHandler = fopen($sCacheAbsolutePath, 'w')) !== false) {
+            $mixedWriteResult = fwrite($rHandler, $sResult);
+            fclose($rHandler);
+            @chmod ($sCacheAbsolutePath, 0666);
+        }
+
+        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)
+    {
+        $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);
+
+            $sResult .= $this->$sMethodWrap($aFile['url']);
+        }
+
+        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));
+
+                $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)
+    {
+        $aKeys = array_keys($aVariables);
+        $aValues = array_values($aVariables);
+
+        $aKeyWrappers = $this->_getKeyWrappers($mixedKeyWrapperHtml);
+
+        $iCountKeys = count($aKeys);
+        for ($i = 0; $i < $iCountKeys; $i++) {
+            if (strncmp($aKeys[$i], 'bx_repeat:', 10) === 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]))
+                        foreach($aValues[$i] as $aValue)
+                            $sValue .= $this->parseHtmlByContent($aMatches[1], $aValue, $mixedKeyWrapperHtml);
+                    else if(is_string($aValues[$i]))
+                        $sValue = $aValues[$i];
+                }
+            } else if (strncmp($aKeys[$i], 'bx_if:', 6) === 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']) && isset($aValues[$i]['condition']) && $aValues[$i]['condition'])
+                        $sValue .= $this->parseHtmlByContent($aMatches[1], $aValues[$i]['content'], $mixedKeyWrapperHtml);
+            } else {
+                $sKey = "'" . $aKeyWrappers['left'] . $aKeys[$i] . $aKeyWrappers['right'] . "'s";
+                $sValue = str_replace('$', '\\$', $aValues[$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, $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 . 'template' . $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)
+    {
+        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 "data:image/" . strtolower($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 . $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 = '')
+    {
+        header("HTTP/1.0 403 Forbidden");
+        $this->displayMsg($sMsg ? $sMsg : _t('_Access denied'));
+    }
+    function displayNoData ($sMsg = '')
+    {
+        header("HTTP/1.0 204 No Content");
+        $this->displayMsg($sMsg ? $sMsg : _t('_Empty'));
+    }
+    function displayErrorOccured ($sMsg = '')
+    {
+        header("HTTP/1.0 500 Internal Server Error");
+        $this->displayMsg($sMsg ? $sMsg : _t('_error occured'));
+    }
+    function displayPageNotFound ($sMsg = '')
+    {
+        header("HTTP/1.0 404 Not Found");
+        $this->displayMsg($sMsg ? $sMsg : _t('_sys_request_page_not_found_cpt'));
+    }
+    function displayMsg ($s, $bTranslate = false)
+    {
+        $sTitle = $bTranslate ? _t($s) : $s;
+        $oTemplate = BxDolTemplate::getInstance();
+        $oTemplate->setPageNameIndex (BX_PAGE_DEFAULT);
+        $oTemplate->setPageHeader ($sTitle);
+        $oTemplate->setPageContent ('page_main_code', DesignBoxContent($sTitle, MsgBox($sTitle), BX_DB_PADDING_DEF));
+        $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;
+
+        header( 'Content-type: text/html; charset=utf-8' );
+        header( 'X-Frame-Options: sameorigin' );
+        echo $oTemplate->parsePageByName('page_' . $oTemplate->getPageNameIndex() . '.html', $oTemplate->getPageContent());
+    }
+}
+
+/** @} */

+ 414 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/classes/BxDolUploader.php

@@ -0,0 +1,414 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolUploaderQuery');
+
+/**
+ * @page objects
+ * @section uploader Uploader
+ * @ref BxDolUploader
+ */
+
+/**
+ * Uploaders are disigned to work as form field in forms. @see BxDolForm.
+ *
+ *
+ * To add file to any form, use the following form field array:
+ * @code
+ * 'attachment' => array(
+ *     'type' => 'files', // this is new form type, which enable upholders automatically
+ *     'storage_object' => 'sample', // the storage object, where uploaded files are going to be saved
+ *     'images_transcoder' => 'sample2', // images transcoder object to use for images preview
+ *     'uploaders' => array ('sys_simple', 'sys_html5'), // the set of uploaders to use to upload files
+ *     'upload_buttons_titles' => array('sys_simple' => 'Upload one by one', 'sys_html5' => 'Upload several files in bulk'); // change default button titles, array with button names, or string to assign to all bnuttons
+ *     'multiple' => true, // allow to upload multiple files per one upload
+ *     'content_id' => 4321, // content id to associate ghost files with
+ *     'ghost_template' => $mixedGhostTemplate, // template for nested form
+ *     'name' => 'attachment', // name of file form field, resulted file id is assigned to this field name
+ *     'caption' => _t('Attachments'), // form field caption
+ * ),
+ * @endcode
+ *
+ *
+ * Available uploaders:
+ * - sys_simple - upload files using standard HTML forms.
+ * - sys_html5 - upload files using AJAX uploader with multiple files selection support (without flash),
+ *   it works in Firefox and WebKit(Safari, Chrome) browsers only, but has fallback for other browsers (IE, Opera).
+ *
+ *
+ * Uploaded files are showed as "nested" forms.
+ * You can pass nested form in 'ghost_template' parameter.
+ * If you don't pass anything in 'ghost_template' parameter, then only file id is passed upon form submission.
+ * The nested form can be declared using the different ways:
+ *
+ *
+ * 1. Pass template as string - just plain string with HTML, for example:
+ *
+ * @code
+ * <div id="bx-uploader-file-{file_id}" class="bx-uploader-ghost">
+ *     <div style="border:2px dotted green; padding:10px; margin-bottom:10px;">
+ *         <input type="hidden" name="f[]" value="{file_id}" />
+ *         {file_name} <br />
+ *         <a href="javascript:void(0);" onclick="{js_instance_name}.deleteGhost('{file_id}')">delete</a>
+ *     </div>
+ * </div>
+ * @endcode
+ *
+ *
+ * 2. Pass form array - regular form array, but with inputs array only, for example:
+ *
+ * @code
+ *  array (
+ *      'inputs' => array (
+ *          'file_name' => array (
+ *              'type' => 'text',
+ *              'name' => 'file_name[]',
+ *              'value' => '{file_title}',
+ *              'caption' => _t('Caption'),
+ *          ),
+ *          'file_desc' => array (
+ *              'type' => 'textarea',
+ *              'name' => 'file_desc[]',
+ *              'caption' => _t('Description'),
+ *          ),
+ *      ),
+ *  );
+ * @endcode
+ *
+ * Array is automatically modified to add necessary form attributes to work as nested form,
+ * file id field is added automatically as hidden input as well.
+ *
+ *
+ * 3. Pass instance of BxDolFormNested class - use BxDolFormNested class or its custom subclass;
+ * to create instance use the same form array as in the previous variant, for example:
+ *
+ * @code
+ * bx_import('BxDolFormNested');
+ * $oFormNested = new BxDolFormNested('attachment', $aFormNested, 'do_submit');
+ * @endcode
+ *
+ * - 'attachment' is the name of file form field from main form.
+ * - $aFormNested is form array from previous example.
+ * - 'do_submit' is main form submit_name; field name of submit form input to determine if form is submitted or not.
+ *
+ *
+ * All 3 variants can have the following replace markers to substitute with real values:
+ * - {file_id} - uploaded file id
+ * - {file_name} - uploaded file name with extension
+ * - {file_title} - uploaded file name without extension
+ * - {file_icon} - URL to file icon automatically determined by file extension
+ * - {file_url} - URL to the original file
+ * - {js_instance_name} - instance of BxDolUploader javascript class
+ *
+ */
+abstract class BxDolUploader extends BxDol
+{
+    protected $_oTemplate;
+
+    protected $_aObject; ///< object properties
+    protected $_sStorageObject; ///< storage object name
+
+    protected $_sUniqId; ///< uniq id used to generate UploaderJsInstance, ResultContainerId, UploadInProgressContainerId and PopupContainerId
+    protected $_sUploaderJsInstance; ///< uplooader js object instance name
+    protected $_sResultContainerId; ///< uploading/uploaded objects container id
+    protected $_sUploadInProgressContainerId; ///< container id where upload in progress element resides
+    protected $_sPopupContainerId; ///< popup container id
+
+    protected $_sUploadErrorMessages; ///< upload error message
+
+    protected $_sButtonTemplate; ///< template name for displaying upload button
+
+    /**
+     * constructor
+     */
+    protected function __construct($aObject, $sStorageObject, $sUniqId)
+    {
+        parent::__construct();
+        $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_aObject = $aObject;
+        $this->_sStorageObject = $sStorageObject;
+
+        $this->_sUniqId = $sUniqId;
+
+        $this->_sUploaderJsInstance = 'glUploader_' . $sUniqId . '_' . $this->_aObject['object'];
+        $this->_sUploadInProgressContainerId = 'bx-form-input-files-' . $sUniqId . '-upload-in-progress-' . $this->_aObject['object'];
+        $this->_sPopupContainerId = 'bx-form-input-files-' . $sUniqId . '-popup-wrapper-' . $this->_aObject['object'];
+
+        $this->_sResultContainerId = 'bx-form-input-files-' . $sUniqId . '-upload-result';
+        $this->_sErrorsContainerId = 'bx-form-input-files-' . $sUniqId . '-errors';
+        $this->_sFormContainerId = 'bx-form-input-files-' . $sUniqId . '-form-cont';
+    }
+
+    static public function getObjectInstance($sObject, $sStorageObject, $sResultContainerId)
+    {
+        $aObject = BxDolUploaderQuery::getUploaderObject($sObject);
+        if (!$aObject || !is_array($aObject) || !$aObject['active'])
+            return false;
+
+        $sClass = $aObject['override_class_name'];
+        if (!empty($aObject['override_class_file']))
+            require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+        else
+            bx_import($sClass);
+
+        $o = new $sClass($aObject, $sStorageObject, $sResultContainerId);
+
+        if (!$o->isInstalled() || !$o->isAvailable())
+            return false;
+
+        return $o;
+    }
+
+    /**
+     * Is uploader available?
+     * @return boolean
+     */
+    function isAvailable()
+    {
+        return $this->_aObject['active'] ? true : false;
+    }
+
+    /**
+     * Are required php modules installed for this uploader ?
+     * @return boolean
+     */
+    public function isInstalled()
+    {
+        return true;
+    }
+
+    public function getNameJsInstanceUploader()
+    {
+        return $this->_sUploaderJsInstance;
+    }
+
+    public function getIdContainerResult()
+    {
+        return $this->_sResultContainerId;
+    }
+
+    public function getIdContainerUploadInProgress()
+    {
+        return $this->_sUploadInProgressContainerId;
+    }
+
+    public function getIdContainerPopup()
+    {
+        return $this->_sPopupContainerId;
+    }
+
+    public function getIdContainerErrors()
+    {
+        return $this->_sErrorsContainerId;
+    }
+
+    /**
+     * Handle uploads here.
+     * @param $mixedFiles as usual $_FILES['some_name'] array, but maybe some other params depending on the uploader
+     * @return nothing, but if some files failed to upload, the actual error message can be determined by calling BxDolUploader::getUploadErrorMessages()
+     */
+    public function handleUploads ($iProfileId, $mixedFiles, $isMultiple = true, $iContentId = false)
+    {
+        bx_import('BxDolStorage');
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+
+        if (false == ($aMultipleFiles = $oStorage->convertMultipleFilesArray($mixedFiles)))
+            $aMultipleFiles = array($mixedFiles);
+
+        if (!$isMultiple)
+            $this->cleanupGhostsForProfile($iProfileId, $iContentId);
+
+        foreach ($aMultipleFiles as $aFile) {
+
+            $iId = $oStorage->storeFileFromForm($aFile, true, $iProfileId, $iContentId);
+            if (!$iId)
+                $this->appendUploadErrorMessage(_t('_sys_uploader_err_msg', $aFile['name'], $oStorage->getErrorString()));
+
+            if (!$isMultiple)
+                break;
+        }
+
+        echo '<script>window.parent.' . $this->getNameJsInstanceUploader() . '.onUploadCompleted(\'' . bx_js_string($this->getUploadErrorMessages(), BX_ESCAPE_STR_APOS) . '\');</script>';
+    }
+
+    public function getUploadErrorMessages ($sFormat = 'HTML')
+    {
+        if ('HTML' == $sFormat)
+            return nl2br($this->_sUploadErrorMessages);
+        else
+            return $this->_sUploadErrorMessages;
+    }
+
+    /**
+     * Show uploader button.
+     * @return HTML string
+     */
+    public function getUploaderButton($mixedGhostTemplate, $isMultiple = true, $aParams = array())
+    {
+        $this->addCssJs ();
+
+        $sJsValue = '';
+        if (is_array($mixedGhostTemplate)) {
+            $sJsValue = "{\n";
+            foreach ($mixedGhostTemplate as $iFileId => $s) {
+                $sJsValue .= $iFileId . ':' . "'" . bx_js_string($s, BX_ESCAPE_STR_APOS) . "',\n";
+            }
+            $sJsValue = substr($sJsValue, 0, -2);
+            $sJsValue .= "}\n";
+        } else {
+            $sJsValue = "'" . bx_js_string($mixedGhostTemplate, BX_ESCAPE_STR_APOS) . "'";
+        }
+
+        $aParamsDefault = array (
+            'uploader_instance_name' => $this->getNameJsInstanceUploader(),
+            'engine' => $this->_aObject['object'],
+            'storage_object' => $this->_sStorageObject,
+            'uniq_id' => $this->_sUniqId,
+            'template_ghost' => $sJsValue,
+            'multiple' => $isMultiple ? 1 : 0,
+        );
+        $aParams = array_merge($aParamsDefault, $aParams);
+        return $this->_oTemplate->parseHtmlByName($this->_sButtonTemplate, $aParams);
+    }
+
+    /**
+     * add necessary js, css files and js translations
+     */ 
+    public function addCssJs ()
+    {
+        $this->_oTemplate->addCss('uploaders.css');
+        $this->_oTemplate->addJs('BxDolUploader.js');
+        $this->_oTemplate->addJsTranslation(array(
+            '_sys_uploader_confirm_leaving_page',
+            '_sys_uploader_confirm_close_popup',
+            '_sys_uploader_upload_canceled',
+        ));
+    }
+
+    /**
+     * Get uploader button title
+     */
+    public function getUploaderButtonTitle($mixed = false)
+    {
+        // it is overrided in child classes
+    }
+
+    /**
+     * Show uploader form.
+     * @return HTML string
+     */
+    public function getUploaderForm($isMultiple = true, $iContentId = false)
+    {
+        // it is overrided in child classes
+    }
+
+    /**
+     * Display uploaded, but not saved files - ghosts
+     * @return HTML or JSON string
+     */
+    public function getGhosts($iProfileId, $sFormat, $sImagesTranscoder = false, $iContentId = false)
+    {
+        bx_import('BxDolStorage');
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+
+        $oImagesTranscoder = false;
+        if ($sImagesTranscoder) {
+            bx_import('BxDolImageTranscoder');
+            $oImagesTranscoder = BxDolImageTranscoder::getObjectInstance($sImagesTranscoder);
+        }
+
+        $a = '';
+        $aGhosts = $oStorage->getGhosts($iProfileId, $iContentId);
+        foreach ($aGhosts as $aFile) {
+            $sFileIcon = $oImagesTranscoder && 0 == strncmp($aFile['mime_type'], 'image/', 6) ? $oImagesTranscoder->getImageUrl($aFile['id']) : $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
+            $a[$aFile['id']] = array (
+                'file_id' => $aFile['id'],
+                'file_name' => $aFile['file_name'],
+                'file_title' => $oStorage->getFileTitle($aFile['file_name']),
+                'file_icon' => $sFileIcon,
+                'file_url' => $oStorage->getFileUrlById($aFile['id']),
+                'js_instance_name' => $this->_sUploaderJsInstance,
+            );
+        }
+
+        if ('json' == $sFormat) {
+            return json_encode($a);
+        } else { // html format is not suported for this data type
+            return false;
+        }
+    }
+
+    /**
+     * Delete file by file id, usually ghost file
+     * @return 'ok' string on success or error string on error
+     */
+    public function deleteGhost($iFileId, $iProfileId)
+    {
+        bx_import('BxDolStorage');
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+
+        $aFile = $oStorage->getFile ($iFileId);
+        if (!$aFile)
+            return _t('_Error Occured');
+
+        if ($aFile['profile_id'] != $iProfileId)
+            return _t('_Error Occured');
+
+        if (!$oStorage->deleteFile($iFileId))
+            return $oStorage->getErrorString();
+
+        return 'ok';
+    }
+
+    /**
+     * Delete all ghosts files for the specified profile
+     * @return number of delete ghost files
+     */
+    public function cleanupGhostsForProfile($iProfileId, $iContentId = false)
+    {
+        $iCount = 0;
+
+        bx_import('BxDolStorage');
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+
+        $aGhosts = $oStorage->getGhosts($iProfileId, $iContentId);
+        foreach ($aGhosts as $aFile)
+            $iCount += $oStorage->afterUploadCleanup($aFile['id'], $iProfileId);
+
+        return $iCount;
+    }
+
+    protected function cleanUploadErrorMessages ()
+    {
+        $this->_sUploadErrorMessages = '';
+    }
+
+    protected function appendUploadErrorMessage ($s)
+    {
+        $this->_sUploadErrorMessages .= ($this->_sUploadErrorMessages ? "\n" : '') . $s;
+    }
+
+    protected function getRestrictionsText ()
+    {
+        $sTextRestrictions = '';
+        bx_import('BxDolStorage');
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+        if (!$oStorage)
+            return '';
+
+        $a = $oStorage->getRestrictionsTextArray(bx_get_logged_profile_id());
+        foreach ($a as $s)
+            $sTextRestrictions .= '<div class="bx-uploader-msg-info bx-def-font-grayed">' . $s . '</div>';
+
+        return $sTextRestrictions;
+    }
+}
+
+/** @} */

+ 501 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/classes/BxDolCmts.js

@@ -0,0 +1,501 @@
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+function BxDolCmts (options) {
+    this._sObjName = undefined == options.sObjName ? 'oCmts' : options.sObjName;    // javascript object name, to run current object instance from onTimer
+    this._sSystem = options.sSystem; // current comment system
+    this._iAuthorId = options.iAuthorId; // this comment's author ID.
+    this._iObjId = options.iObjId; // this object id comments
+    this._sActionsUrl = options.sRootUrl + 'cmts.php'; // actions url address
+
+    this._sPostFormPosition = undefined == options.sPostFormPosition ? 'top' : options.sPostFormPosition;
+    this._sDisplayType = undefined == options.sDisplayType ? 'threaded' : options.sDisplayType;
+    this._sBrowseType = undefined == options.sBrowseType ? 'tail' : options.sBrowseType;
+    this._sBrowseFilter = undefined == options.sBrowseFilter ? 'all' : options.sBrowseFilter;
+
+    this._sAnimationEffect = 'fade';
+    this._iAnimationSpeed = 'slow';
+
+    this._oSavedTexts = {};
+    this._sRootId = '#cmts-box-' + this._sSystem + '-' + this._iObjId;
+
+    // init post comment form
+    if ($(this._sRootId + ' .cmt-post-reply form').length) {
+    	var sFormId = $(this._sRootId + ' .cmt-post-reply form').attr('id');
+    	this.cmtInitFormPost(sFormId);
+    }
+}
+
+/*--- Main layout functionality ---*/
+BxDolCmts.prototype.cmtInitFormPost = function(sCmtFormId)
+{
+	var $this = this;
+	var oCmtForm = $('#' + sCmtFormId);
+
+	oCmtForm.ajaxForm({
+        dataType: "json",
+        beforeSubmit: function (formData, jqForm, options) {
+        	window[$this._sObjName].cmtBeforePostSubmit(oCmtForm);
+        },
+        success: function (oData) {
+        	window[$this._sObjName].cmtAfterPostSubmit(oCmtForm, oData);
+        }
+    });
+};
+
+BxDolCmts.prototype.cmtBeforePostSubmit = function(oCmtForm)
+{
+	this._loadingInButton($(oCmtForm).children().find(':submit'), true);
+};
+
+BxDolCmts.prototype.cmtAfterPostSubmit = function (oCmtForm, oData)
+{
+	this._loadingInButton($(oCmtForm).children().find(':submit'), false);
+
+	if(oData && oData.msg != undefined)
+        alert(oData.msg);
+
+	if(oData && oData.id != undefined) {
+		var iCmtId = parseInt(oData.id);
+        if(iCmtId > 0) {
+        	var $this = this;
+
+            this._getCmt(oCmtForm, iCmtId);
+            this._getForm(undefined, parseInt(oData.parent_id), function(sForm) {
+            	var oForm = $(sForm);
+            	oCmtForm.parents('.cmt-reply:first').hide().html(oForm.html()).show();
+
+            	var sFormId = oForm.find('form').attr('id');
+            	$this.cmtInitFormPost(sFormId);
+            });
+        }
+
+        return;
+	}
+
+	if(oData && oData.form != undefined && oData.form_id != undefined) {
+		$('#' + oData.form_id).replaceWith(oData.form);
+		this.cmtInitFormPost(oData.form_id);
+
+		return;
+	}
+};
+
+BxDolCmts.prototype.cmtInitFormEdit = function(sCmtFormId)
+{
+	var $this = this;
+	var oCmtForm = $('#' + sCmtFormId);
+
+	oCmtForm.ajaxForm({
+        dataType: "json",
+        beforeSubmit: function (formData, jqForm, options) {
+        	window[$this._sObjName].cmtBeforeEditSubmit(oCmtForm);
+        },
+        success: function (oData) {
+        	window[$this._sObjName].cmtAfterEditSubmit(oCmtForm, oData);
+        }
+    });
+};
+
+BxDolCmts.prototype.cmtBeforeEditSubmit = function(oCmtForm)
+{
+	this._loadingInButton($(oCmtForm).children().find(':submit'), true);
+};
+
+BxDolCmts.prototype.cmtAfterEditSubmit = function (oCmtForm, oData)
+{
+	var $this = this;
+	this._loadingInButton($(oCmtForm).children().find(':submit'), false);
+
+	if(oData && oData.msg != undefined)
+        alert(oData.msg);
+
+	if(oData && oData.id != undefined && oData.text != undefined) {
+		var iCmtId = parseInt(oData.id);
+		if(iCmtId > 0) {
+			$('#cmt' + iCmtId + ' .cmt-body').bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function() {
+				$(this).html(oData.text).bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+			});
+        }
+
+        return;
+	}
+
+	if(oData && oData.form != undefined && oData.form_id != undefined) {
+		$('#' + oData.form_id).replaceWith(oData.form);
+		this.cmtInitFormEdit(oData.form_id);
+
+		return;
+	}
+};
+
+BxDolCmts.prototype.cmtEdit = function(oLink, iCmtId) {
+    var $this = this;
+    var oParams = this._getDefaultActions();
+    oParams['action'] = 'GetFormEdit';
+    oParams['Cmt'] = iCmtId;
+
+    $(oLink).parents('.cmt-comment-manage:first').dolPopupHide();
+
+    var sBodyId = this._sRootId + ' #cmt' + iCmtId + ' .cmt-body:first';
+    if ($(sBodyId + ' > form').length) {
+        $(sBodyId).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+            $(this).html($this._oSavedTexts[iCmtId]).bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+        });
+        return;
+    }
+    else
+        this._oSavedTexts[iCmtId] = $(sBodyId).html();
+
+    this._loadingInContent (oLink, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oParams,
+        function (oData) {
+        	$this._loadingInContent (oLink, false);
+
+        	if(oData && oData.msg != undefined)
+                alert(oData.msg);
+
+        	if(oData && oData.form != undefined && oData.form_id != undefined) {
+        		$(sBodyId).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+                    $(this).html(oData.form).bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+                    	$this.cmtInitFormEdit(oData.form_id);
+                    });
+                });
+        	}
+        },
+        'json'
+    );
+};
+
+BxDolCmts.prototype.cmtRemove = function(e, iCmtId) {
+    if (!this._confirm()) 
+    	return;
+
+    var $this = this;
+    var oParams = this._getDefaultActions();
+    oParams['action'] = 'Remove';
+    oParams['Cmt'] = iCmtId;
+
+    this._loadingInContent(e, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oParams,
+        function(oData) {
+            $this._loadingInContent(e, false);
+
+            if(oData && oData.msg != undefined)
+                alert(oData.msg);
+
+            if(oData && oData.id != undefined) {
+            	$('#cmt' + oData.id).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+                	var oCounter = $(this).parent('ul.cmts').siblings('.cmt-cont').find('.cmt-actions a.cmt-comment-replies span');
+                	if(oCounter)
+                		oCounter.html(oCounter.html() - 1);
+
+                	$(this).remove();
+                });
+            }
+        },
+        'json'
+    );
+};
+
+BxDolCmts.prototype.cmtLoad = function(oLink, iCmtParentId, iStart, iPerView)
+{
+	var $this = this;
+	var bButton = $(oLink).hasClass('bx-btn');
+
+	if(bButton)
+		this._loadingInButton(oLink, true);
+	else 
+		this._loading($(oLink).parents('ul.cmts:first'), true);
+
+	this._getCmts(null, iCmtParentId, iStart, iPerView, this._sDisplayType, this._sBrowseType, this._sBrowseFilter, function(sListId, sContent) {
+		if(bButton)
+			$this._loadingInButton(oLink, false);
+		else 
+			$this._loading($(oLink).parents('ul.cmts:first'), false);
+
+		$this._cmtsReplace($(oLink).parents('li:first'), sContent);
+	});
+};
+
+BxDolCmts.prototype.cmtChangeDisplay = function(oLink, sType)
+{
+	var $this = this;
+	this._getCmts(oLink, 0, undefined, undefined, sType, this._sBrowseType, this._sBrowseFilter, function(sListId, sContent) {
+		$this._sDisplayType = sType;
+		$this._cmtsReplaceContent($(sListId), sContent);
+	});
+};
+
+BxDolCmts.prototype.cmtChangeBrowse = function(oLink, sType)
+{
+	var $this = this;
+	this._getCmts(oLink, 0, undefined, undefined, this._sDisplayType, sType, this._sBrowseFilter, function(sListId, sContent) {
+		$this._sBrowseType = sType;
+		$this._cmtsReplaceContent($(sListId), sContent);
+	});
+};
+
+BxDolCmts.prototype.cmtChangeFilter = function(oLink, sType)
+{
+	var $this = this;
+	this._getCmts(oLink, 0, undefined, undefined, this._sDisplayType, $this._sBrowseType, sType, function(sListId, sContent) {
+		$this._sBrowseFilter = sType;
+		$this._cmtsReplaceContent($(sListId), sContent);
+	});
+};
+BxDolCmts.prototype.showLiveUpdate = function(oData)
+{
+	if(!oData.code)
+		return;
+
+	var iCountNew = parseInt(oData.count_new);
+	var iCountOld = parseInt(oData.count_old);
+	if(iCountNew <= iCountOld)
+		return;
+
+	var oNotification = $(oData.code);
+	oNotification.find('.cmt-message span').html(iCountNew - iCountOld);
+
+	var sId = oNotification.attr('id');
+	$('#' + sId).remove();
+
+    $(oNotification).prependTo('body').dolPopup({
+    	position: 'fixed',
+    	top: ($(window).height() - oNotification.height()) + 'px',
+    	left: ($(window).width() - oNotification.width())/2 + 'px',
+    	fog: false
+    });
+};
+BxDolCmts.prototype.showReplacement = function(iCmtId)
+{
+    $('#cmt' + iCmtId + '-hidden').bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function(){
+        $(this).next('#cmt' + iCmtId).bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+    });
+};
+
+BxDolCmts.prototype.showMore = function(oLink)
+{
+	$(oLink).parent('span').next('span').show().prev('span').remove();
+};
+
+BxDolCmts.prototype.showImage = function(oLink, sUrl) {
+	$(this._sRootId + '-view-image-popup').dolPopupImage(sUrl, $(oLink).parent());
+};
+
+BxDolCmts.prototype.toggleReply = function(e, iCmtParentId)
+{
+	var $this = this;
+	var sParentId = this._sRootId + ' #cmt' + iCmtParentId;
+	var fOnShow = function() {
+		$(this).find('textarea:first').focus();
+	};
+
+	var sReplyId = sParentId + ' > .cmt-reply';
+    if ($(sReplyId).length) {
+		$(sReplyId).bx_anim('toggle', this._sAnimationEffect, this._iAnimationSpeed, fOnShow);
+		return;
+    }
+
+	this._getForm(e, iCmtParentId, function(sForm) {
+		var oForm = $(sForm).hide().addClass('cmt-reply-' + $this._sPostFormPosition).addClass('cmt-reply-margin');
+        var oFormSibling = $(sParentId + ' > ul.cmts:first');
+        switch($this._sPostFormPosition) {
+        	case 'top':
+        		oFormSibling.before(oForm);
+        		break;
+
+        	case 'bottom':
+        		oFormSibling.after(oForm);
+        		break;
+        }
+
+        var sFormId = oForm.find('form').attr('id');
+        $this.cmtInitFormPost(sFormId);
+
+    	$(sParentId).children('.cmt-reply').bx_anim('toggle', $this._sAnimationEffect, $this._iAnimationSpeed, fOnShow);    		
+	});
+};
+
+BxDolCmts.prototype.toggleManagePopup = function(oLink, iCmtId) {
+	var oPopup = $('#' + this._sSystem + '-manage-' + iCmtId);
+	if(oPopup.is(':visible')) {
+		oPopup.dolPopupHide();
+		return;
+	}
+
+	oPopup.dolPopup({
+		pointer:{
+			el:$(oLink)
+		}
+	});
+};
+
+BxDolCmts.prototype._getCmt = function (e, iCmtId)
+{
+    var $this = this;
+    var oData = this._getDefaultActions();
+    oData['action'] = 'GetCmt';
+    oData['Cmt'] = iCmtId;
+    oData['CmtBrowse'] = this._sBrowseType;
+    oData['CmtDisplay'] = this._sDisplayType;
+
+    this._loadingInBlock (e);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oData,
+        function (oData) {
+            $this._loadingInBlock (e, false);
+
+            var sListId = $this._sRootId + ' #cmt' + oData.vparent_id + ' > ul';
+            var sReplyFormId = $this._sRootId + ' #cmt' + oData.parent_id + ' > .cmt-reply';
+
+            //--- Hide reply form ---//
+            if($(sReplyFormId).length)
+            	$(sReplyFormId).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed);
+
+            //--- Some number of comments already loaded ---//
+            if($(sListId + ' > li.cmt').length)
+                $(sListId + ' > li.cmt:last').after($(oData.content).hide()).next('li.cmt:hidden').bxTime().bx_anim('toggle', $this._sAnimationEffect, $this._iAnimationSpeed);
+            //-- There is no comments at all ---//
+            else
+            	$(sListId).hide().html(oData.content).bxTime().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+        },
+        'json'
+    );
+};
+
+BxDolCmts.prototype._getCmts = function (e, iCmtParentId, iStart, iPerView, sDisplayType, sBrowseType, sBrowseFilter, onLoad)
+{
+    var $this = this;
+    var oData = this._getDefaultActions();    
+    oData['action'] = 'GetCmts';
+    oData['CmtParent'] = iCmtParentId;
+    if(parseInt(iStart) >= 0)
+        oData['CmtStart'] = iStart;
+    if(parseInt(iPerView) >= 0)
+        oData['CmtPerView'] = iPerView;
+    if(sDisplayType)
+        oData['CmtDisplay'] = sDisplayType;
+    if(sBrowseType)
+        oData['CmtBrowse'] = sBrowseType;
+    if(sBrowseFilter)
+        oData['CmtFilter'] = sBrowseFilter;
+
+    var sListId =  this._sRootId + ' #cmt' + iCmtParentId + ' > ul:first';
+
+    if(e)
+        this._loadingInBlock(e, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oData,
+        function(s) {
+        	if(e)
+        		$this._loadingInBlock(e, false);
+
+        	if(typeof onLoad == 'function')
+    			onLoad(sListId, s);
+        	else
+        		$this._cmtsAppend(sListId, s);
+        }
+    );
+};
+
+BxDolCmts.prototype._getForm = function (e, iCmtParentId, onLoad)
+{
+	var $this = this;
+
+    var oData = this._getDefaultActions();
+    oData['action'] = 'GetFormPost';
+    oData['CmtType'] = 'reply';
+    oData['CmtParent'] = iCmtParentId;
+    oData['CmtBrowse'] = this._sBrowseType;
+    oData['CmtDisplay'] = this._sDisplayType;
+
+    if(e)
+    	this._loadingInContent(e, true);
+
+    jQuery.post (
+        this._sActionsUrl,
+        oData,
+        function (s) {
+        	if(e)
+        		$this._loadingInContent(e, false);
+
+            if(typeof onLoad == 'function')
+    			onLoad(s);
+        }
+    );
+};
+
+BxDolCmts.prototype._cmtsAppend = function(sIdTo, sContent)
+{
+	$(sIdTo).append($(sContent).hide()).children(':hidden').bxTime().bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+};
+
+BxDolCmts.prototype._cmtsPrepend = function(sIdTo, sContent)
+{
+	$(sIdTo).prepend($(sContent).hide()).children(':hidden').bxTime().bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+};
+
+BxDolCmts.prototype._cmtsReplace = function(oReplace, sContent)
+{
+	var $this = this;
+	$(oReplace).bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function() {
+		$(this).after($(sContent).hide()).nextAll(':hidden').bxTime().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+		$(this).remove();
+	});
+};
+BxDolCmts.prototype._cmtsReplaceContent = function(oParent, sContent)
+{
+	var $this = this;
+	$(oParent).bx_anim('hide', this._sAnimationEffect, this._iAnimationSpeed, function() {
+		$(this).html(sContent).bxTime().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+	});
+};
+
+BxDolCmts.prototype._getDefaultActions = function() {
+	var oDate = new Date();
+    return {
+        sys: this._sSystem,
+        id: this._iObjId,
+        _t: oDate.getTime()
+    };
+};
+
+BxDolCmts.prototype._loading = function(e, bShow) {
+	var oParent = $(e).length ? $(e) : $('body'); 
+	bx_loading(oParent, bShow);
+};
+
+BxDolCmts.prototype._loadingInBlock = function(e, bShow) {
+	var oParent = $(e).length ? $(e).parents('.bx-db-content:first') : $('body'); 
+	bx_loading(oParent, bShow);
+};
+
+BxDolCmts.prototype._loadingInContent = function(e, bShow) {
+	var oParent = $(e).length ? $(e).parents('li.cmt:first,.cmt-reply:first') : $('body'); 
+	bx_loading(oParent, bShow);
+};
+
+BxDolCmts.prototype._loadingInButton = function(e, bShow) {
+	if($(e).length)
+		bx_loading_btn($(e), bShow);
+	else
+		bx_loading($('body'), bShow);	
+};
+
+BxDolCmts.prototype._confirm = function() {
+    return confirm(_t('_are you sure?'));
+};

+ 61 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/classes/BxDolLiveUpdates.js

@@ -0,0 +1,61 @@
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+function BxDolLiveUpdates(oOptions)
+{
+	this._sActionsUrl = oOptions.sActionsUrl == undefined ? sUrlRoot + 'live_updates.php' : oOptions.sActionsUrl;
+	this._sObjName = oOptions.sObjName == undefined ? 'oLiveUpdates' : oOptions.sObjName;
+	this._iInterval = oOptions.iInterval == undefined ? 3000 : oOptions.iInterval;
+	this._bServerRequesting = oOptions.bServerRequesting == undefined ? {} : oOptions.bServerRequesting;
+
+	this._iHandler = 0;
+
+	this.init();
+}
+
+BxDolLiveUpdates.prototype.init = function() {
+	var $this = this;
+
+	if(this._iHandler)
+		this.destroy();
+
+    $(document).ready(function() {
+    	$this._iHandler = setInterval(function() {
+    		$this.perform();
+    	}, $this._iInterval);
+    });
+};
+
+BxDolLiveUpdates.prototype.destroy = function() {
+	if(this._iHandler)
+		clearInterval(this._iHandler);
+};
+
+BxDolLiveUpdates.prototype.perform = function() {
+	if(!this._bServerRequesting)
+		return;
+
+	var $this = this;
+	var oDate = new Date();
+
+    $.get(
+    	this._sActionsUrl,
+        {
+    		_t: oDate.getTime()
+        },
+        function(aData) {
+        	$.each(aData, function(iIndex, oValue) {
+        		if(oValue.method) {
+        			var oFunc = function(oData) {
+        				eval(oValue.method);
+        			};
+        			oFunc(oValue.data);
+        		}
+        	});
+        },
+        'json'
+    );
+};

+ 315 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/classes/BxDolUploader.js

@@ -0,0 +1,315 @@
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+/**
+ * Simple Uploader js class
+ */
+function BxDolUploaderSimple (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._sIframeId = 'bx-form-input-files-' + sUniqId + '-iframe';
+    this._eForm = null;
+}
+
+BxDolUploaderSimple.prototype.init = function (sUploaderObject, sStorageObject, sUniqId, options) {    
+    this._isUploadsInProgress = false;
+
+    this._sUploaderObject = sUploaderObject;
+    this._sStorageObject = sStorageObject;
+
+    this._sUniqId = sUniqId;
+
+    this._sUploaderJsInstance = 'glUploader_' + sUniqId + '_' + this._sUploaderObject;
+    this._sUploadInProgressContainerId = 'bx-form-input-files-' + sUniqId + '-upload-in-progress-' + this._sUploaderObject;
+    this._sPopupContainerId = 'bx-form-input-files-' + sUniqId + '-popup-wrapper-' + this._sUploaderObject;
+
+    this._sResultContainerId = 'bx-form-input-files-' + sUniqId + '-upload-result';
+    this._sErrorsContainerId = 'bx-form-input-files-' + sUniqId + '-errors'; 
+
+    this._sFormContainerId = 'bx-form-input-files-' + sUniqId + '-form-cont';
+
+    this._sTemplateGhost = options.template_ghost ? options.template_ghost : '<div id="bx-uploader-file-{file_id}"><input type="hidden" name="f[]" value="{file_id}" />{file_name} (<a href="javascript:void(0);" onclick="{js_instance_name}.deleteGhost(\'{file_id}\')">delete</a>)</div>';
+    this._sTemplateError = options.template_error_msg ? options.template_error_msg : '<div>{error}</div>' ;
+    this._sTemplateErrorGhosts = options.template_error_ghosts ? options.template_error_ghosts : this._sTemplateError;
+
+    this._isMultiple = undefined == options.multiple || !options.multiple ? false : true;
+
+    this._iContentId = undefined == options.content_id || '' == options.content_id ? '' : parseInt(options.content_id);
+
+    this._sImagesTranscoder = options.images_transcoder ? options.images_transcoder : '';
+
+    this._isErrorShown = false;
+}
+
+BxDolUploaderSimple.prototype.isMultiple = function () {    
+    return this._isMultiple;
+}
+
+BxDolUploaderSimple.prototype.getCurrentFilesCount = function () {
+    return $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').length;
+}
+
+BxDolUploaderSimple.prototype.showUploaderForm = function () {    
+    var $this = this;
+    var sUrl = this._getUrlWithStandardParams() + '&a=show_uploader_form&m=' + (this._isMultiple ? 1 : 0) + '&c=' + this._iContentId + '&_t=' + escape(new Date());
+
+    $(window).dolPopupAjax({
+        url: sUrl,
+        id: {force: true, value: this._sPopupContainerId},
+        onBeforeShow: function() {
+            if ($this.isMultiple())
+                $('#' + $this._sPopupContainerId + ' .bx-uploader-add-more-files').show();
+            else
+                $('#' + $this._sPopupContainerId + ' .bx-uploader-add-more-files').hide();
+            $('#' + $this._sPopupContainerId + ' .bx-popup-element-close').click(function() {
+                $this.onClickCancel();
+            });
+        },
+        closeElement: false,
+        closeOnOuterClick: false
+    });
+}
+
+BxDolUploaderSimple.prototype.onClickCancel = function () {
+    if (this._isUploadsInProgress) {
+        if (confirm(_t('_sys_uploader_confirm_close_popup'))) {
+            this.cancelAll();
+            $('#' + this._sPopupContainerId).dolPopupHide({});
+        }
+    } else {
+        $('#' + this._sPopupContainerId).dolPopupHide();
+    }
+}
+
+BxDolUploaderSimple.prototype.onBeforeUpload = function (params) {
+
+    this._eForm = params;
+
+    this._loading(true, false);
+        
+    this._isUploadsInProgress = true;
+    this._lockPageFromLeaving();
+}
+
+BxDolUploaderSimple.prototype.onProgress = function (params) {
+
+}
+
+BxDolUploaderSimple.prototype.onUploadCompleted = function (sErrorMsg) {
+
+    this._isUploadsInProgress = false;
+    this._unlockPageFromLeaving();
+
+    if (sErrorMsg.length) {
+        this.restoreGhosts();
+        this._loading(false, true);
+        this._showError(sErrorMsg);
+    } else {        
+        this.restoreGhosts();
+        $('#' + this._sPopupContainerId).dolPopupHide({});
+    }
+}
+
+BxDolUploaderSimple.prototype.cancelAll = function () {
+    $('#' + this._sIframeId).attr('src', 'javascript:false;');
+    this.onUploadCompleted(_t('_sys_uploader_upload_canceled'));
+}
+
+BxDolUploaderSimple.prototype.restoreGhosts = function () {
+    var sUrl = this._getUrlWithStandardParams() + '&img_trans=' + this._sImagesTranscoder + '&a=restore_ghosts&f=json' + '&c=' + this._iContentId + '&_t=' + (new Date());
+    var $this = this;
+
+    bx_loading(this._sResultContainerId, true);
+
+    $.getJSON(sUrl, function (aData) {
+
+        bx_loading($this._sResultContainerId, false);
+
+        if (!$this.isMultiple())
+            $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').remove();
+
+        $.each(aData, function(iFileId, oVars) {
+            if ($('#bx-uploader-file-' + iFileId).length > 0)
+                return;
+            var sHTML;
+            if (typeof $this._sTemplateGhost == 'object')
+                sHTML = $this._sTemplateGhost[iFileId];
+            else
+                sHTML = $this._sTemplateGhost;
+            for (var i in oVars)
+                sHTML = sHTML.replace (new RegExp('{'+i+'}', 'g'), oVars[i]);
+            
+            $('#' + $this._sResultContainerId).prepend(sHTML);
+
+            $('#bx-uploader-file-' + iFileId + ' .bx-uploader-ghost-preview img').hide().fadeIn(1000);
+
+        });
+    });
+}
+
+BxDolUploaderSimple.prototype.deleteGhost = function (iFileId) {
+    var sUrl = this._getUrlWithStandardParams() + '&a=delete&id=' + iFileId;
+    var $this = this;
+
+    bx_loading('bx-uploader-file-' + iFileId, true);
+
+    $.post(sUrl, function (sMsg) {
+        bx_loading('bx-uploader-file-' + iFileId, false);
+        if ('ok' == sMsg) {
+            $('#bx-uploader-file-' + iFileId).slideUp('slow', function () {
+                $('#bx-uploader-file-' + iFileId).remove();
+            });
+        } else {
+            $('#' + this._sResultContainerId).prepend($this._sTemplateErrorGhosts.replace('{error}', sMsg));
+        }        
+    });
+}
+
+BxDolUploaderSimple.prototype._showError = function (s, bAppend) {
+    if (s == undefined || !s.length)
+        return;
+    if (!bAppend)
+        $('#' + this._sPopupContainerId + ' #' + this._sErrorsContainerId).html(this._sTemplateError.replace('{error}', s));
+    else
+        $('#' + this._sPopupContainerId + ' #' + this._sErrorsContainerId).prepend(this._sTemplateError.replace('{error}', s));
+    this._isErrorShown = true;
+}
+
+BxDolUploaderSimple.prototype._getUrlWithStandardParams = function () {
+    return sUrlRoot + 'storage_uploader.php?uo=' + this._sUploaderObject + '&so=' + this._sStorageObject + '&uid=' + this._sUniqId;
+}
+
+BxDolUploaderSimple.prototype._lockPageFromLeaving = function () {
+    $(window).bind('beforeunload', function (e) {
+        var e = e || window.event;
+        // for ie, ff
+        e.returnValue = _t('_sys_uploader_confirm_leaving_page');
+        // for webkit
+        return _t('_sys_uploader_confirm_leaving_page');
+    });
+}
+
+BxDolUploaderSimple.prototype._unlockPageFromLeaving = function () {
+    $(window).unbind('beforeunload');
+}
+
+BxDolUploaderSimple.prototype._loading = function (bShowProgress, bShowForm) {
+
+    var eForm = $('#' + this._sFormContainerId + ' .bx-uploader-files-list');
+    var eBtn = $('#' + this._sFormContainerId + ' .bx-btn-primary');
+
+    if (bShowForm) {
+        if (null != this._eForm) {
+            eForm.find('.bx-uploader-simple-file').filter(':not(:first)').remove();
+            this._eForm.reset();
+        }
+        eForm.show();
+        eBtn.show();
+    } else {
+        eForm.hide();
+        eBtn.hide();
+    }
+
+    bx_loading($('#' + this._sFormContainerId + ' .bx-uploader-loading').get(0), bShowProgress);
+}
+
+/**
+ * HTML5 Uploader js class
+ */
+function BxDolUploaderHTML5 (sUploaderObject, sStorageObject, sUniqId, options) {
+
+    this.init(sUploaderObject, sStorageObject, sUniqId, options);
+
+    this._sIframeId = 'bx-form-input-files-' + sUniqId + '-iframe';
+    this._eForm = null;    
+
+    this._sDivId = 'bx-form-input-files-' + sUniqId + '-div-' + this._sUploaderObject;    
+
+    this._uploader = null;
+
+    this.initUploader = function (o) {
+
+        var $this = this;
+
+        if (null != this._uploader)
+            this._uploader = null;
+
+        var _options = {            
+            element: $('#' + this._sDivId).get(0),
+            action: sUrlRoot + 'storage_uploader.php',
+            multiple: $this.isMultiple(),
+            params: {
+                uo: this._sUploaderObject,
+                so: this._sStorageObject,
+                uid: this._sUniqId,
+                m: this._isMultiple ? 1 : 0,
+                c: this._iContentId,
+                a: "upload"
+            },
+            onSubmit: function(id, fileName){
+                $this.onBeforeUpload('');
+            },
+            onProgress: function(id, fileName, loaded, total){
+                $this.onProgress({
+                    id: id, 
+                    fileName: fileName, 
+                    loaded: loaded, 
+                    total: total
+                });
+            },
+            onComplete: function(id, fileName, responseJSON){
+                $this.onUploadCompleted('');
+            },
+
+            onCancel: function(id, fileName){
+                $this.onUploadCompleted(_t('_sys_uploader_upload_canceled'));
+            },
+
+            messages: {
+                onLeave: _t('_sys_uploader_confirm_leaving_page')
+            },
+
+            showMessage: function(message){ 
+                $this._showError(message, true); 
+            }
+
+        };
+        qq.extend(_options, o);
+        this._uploader = new qq.FileUploader(_options);
+    }
+
+    this.onUploadCompleted = function (sErrorMsg) {        
+
+        if (sErrorMsg.length)
+            this._showError(sErrorMsg);        
+        
+        if (0 == this._uploader.getInProgress()) {
+            this._isUploadsInProgress = false;
+            this.restoreGhosts();
+            if (!this._isErrorShown)
+                $('#' + this._sPopupContainerId).dolPopupHide({});
+        }
+    }
+
+    this.cancelAll = function () {
+        this._uploader._handler.cancelAll();
+        $('#' + this._sFormContainerId + ' .' + this._uploader._options.classes.list).html('');        
+    }
+
+    this.onBeforeUpload = function (params) {
+        this._isUploadsInProgress = true;
+    }
+
+    this.onProgress = function (params) {
+
+    }
+
+}
+
+BxDolUploaderHTML5.prototype = BxDolUploaderSimple.prototype;
+
+

+ 619 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/functions.js

@@ -0,0 +1,619 @@
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+
+
+function getHtmlData( elem, url, callback, method , confirmation)
+{
+    if ('undefined' != typeof(confirmation) && confirmation && !confirm(_t('_are you sure?'))) 
+        return false;
+
+    // in most cases it is element ID, in other cases - object of jQuery
+    if (typeof elem == 'string')
+        elem = '#' + elem; // create selector from ID
+
+    var $block = $(elem);
+
+    var blockPos = $block.css('position');
+
+    $block.css('position', 'relative'); // set temporarily for displaying "loading icon"
+
+    bx_loading_content($block, true);
+    var $loadingDiv = $block.find('.bx-loading-ajax');
+
+    var iLeftOff = parseInt(($block.innerWidth() / 2.0) - ($loadingDiv.outerWidth()  / 2.0));
+    var iTopOff  = parseInt(($block.innerHeight() / 2.0) - ($loadingDiv.outerHeight()));
+    if (iTopOff<0) iTopOff = 0;
+
+    $loadingDiv.css({
+        position: 'absolute',
+        left: iLeftOff,
+        top:  iTopOff
+    });
+
+    if (undefined != method && (method == 'post' || method == 'POST')) {
+
+        $.post(url, function(data) {
+
+            $block.html(data);
+	        $block.css('position', blockPos).bxTime();
+            if ($.isFunction($.fn.addWebForms))
+                $block.addWebForms();
+
+            bx_activate_anim_icons();
+
+            if (typeof callback == 'function')
+                callback.apply($block);
+        });
+
+    } else {
+
+        $block.load(url + '&_r=' + Math.random(), function() {
+
+	        $(this).css('position', blockPos).bxTime();
+            if ($.isFunction($.fn.addWebForms))
+                $(this).addWebForms();
+
+            bx_activate_anim_icons();
+
+            if (typeof callback == 'function')
+                callback.apply(this);
+        });
+
+    }
+}
+
+function loadDynamicBlockAutoPaginate (e, iStart, iPerPage, sAdditionalUrlParams) {
+
+    sUrl = location.href;
+
+    sUrl = sUrl.replace(/start=\d+/, '').replace(/per_page=\d+/, '').replace(/[&\?]+$/, '');
+    sUrl = bx_append_url_params(sUrl, 'start=' + parseInt(iStart) + '&per_page=' + parseInt(iPerPage));
+    if ('undefined' != typeof(sAdditionalUrlParams))
+        sUrl = bx_append_url_params(sUrl, sAdditionalUrlParams);
+
+    return loadDynamicBlockAuto(e, sUrl);
+}
+
+/**
+ * This function reloads page block automatically, 
+ * just provide any element inside the block and this function will reload the block.
+ * @param e - element inside the block
+ * @return true on success, or false on error - particularly, if block isn't found
+ */
+function loadDynamicBlockAuto(e, sUrl) {
+    var sId = $(e).parents('.bx-page-block-container:first').attr('id');
+
+    if ('undefined' == typeof(sUrl))
+        sUrl = location.href;
+
+    if (!sId || !sId.length)
+        return false;
+
+    var aMatches = sId.match(/\-(\d+)$/);
+    if (!aMatches || !aMatches[1])
+        return false;
+        
+    loadDynamicBlock(parseInt(aMatches[1]), sUrl);
+    return true;
+}
+
+function loadDynamicBlock(iBlockID, sUrl) {
+    getHtmlData($('#bx-page-block-' + iBlockID), bx_append_url_params(sUrl, 'dynamic=tab&pageBlock=' + iBlockID));
+    return true;
+}
+
+function loadDynamicPopupBlock(iBlockID, sUrl) {
+    if (!$('#dynamicPopup').length) {
+        $('<div id="dynamicPopup" style="display:none;"></div>').prependTo('body');
+    }
+
+    $('#dynamicPopup').load(
+        (sUrl + '&dynamic=popup&pageBlock=' + iBlockID),
+        function() {
+            $(this).dolPopup({
+                left: 0,
+                top: 0
+            });
+        }
+    );
+}
+
+function closeDynamicPopupBlock() {
+    $('#dynamicPopup').dolPopupHide();
+}
+
+
+/**
+ * Translate string
+ */
+function _t(s, arg0, arg1, arg2) {
+    if (!window.aDolLang || !aDolLang[s])
+        return s;
+
+    cs = aDolLang[s];
+    cs = cs.replace(/\{0\}/g, arg0);
+    cs = cs.replace(/\{1\}/g, arg1);
+    cs = cs.replace(/\{2\}/g, arg2);
+    return cs;
+}
+
+function showPopupAnyHtml(sUrl, sId) {
+
+    var oPopupOptions = {};
+
+    if (!sId || !sId.length)
+        sId = 'login_div';
+
+    $('#' + sId).remove();
+    $('<div id="' + sId + '" style="display: none;"></div>').prependTo('body').load(
+        sUrl.match('^http[s]{0,1}:\/\/') ? sUrl : sUrlRoot + sUrl,
+        function() {
+            $(this).dolPopup(oPopupOptions);
+        }
+    );
+}
+
+
+function bx_loading_btn (e, b) {
+    e = $(e);
+
+    if (e.children('div').size())
+        e = e.children('div').first();
+
+    if (!b) {
+        e.find('.bx-loading-ajax-btn').remove();
+    } else if (!e.find('.bx-loading-ajax-btn').length) {
+        e.append('<b class="bx-loading-ajax-btn"></b>');
+        new Spinner(aSpinnerSmallOpts).spin(e.find('.bx-loading-ajax-btn').get(0));
+    }
+}
+
+function bx_loading_animate (e) {
+    e = $(e);
+    if (!e.length)
+        return false;
+    if (e.find('.bx-sys-spinner').length)
+        return false;
+    return new Spinner(aSpinnerOpts).spin(e.get(0));
+}
+
+function bx_loading_content (elem, b, isReplace) {
+    var block = $(elem);
+    if (!b) {
+        block.find(".bx-loading-ajax").remove();
+    } else if (!block.find('.bx-loading-ajax').length) {
+        if ('undefined' != typeof(isReplace) && isReplace)
+            block.html('<div class="bx-loading-ajax bx-def-z-index-front" style="position:static;"></div>');
+        else
+            block.append('<div class="bx-loading-ajax bx-def-z-index-front"></div>');
+        bx_loading_animate(block.find('.bx-loading-ajax'));
+    } 
+}
+
+function bx_loading (elem, b) {
+
+    if (typeof elem == 'string')
+        elem = '#' + elem;
+
+    var block = $(elem);
+
+    if (block.hasClass('bx-btn')) {
+        bx_loading_btn (block, b);
+        return;
+    }
+
+    if (1 == b || true == b) {
+
+        bx_loading_content(block, b);
+
+        e = block.find(".bx-loading-ajax");
+        e.css('left', parseInt(block.width()/2.0 - e.width()/2.0));
+
+        var he = e.outerHeight();
+        var hc = block.outerHeight();
+
+        if (block.css('position') != 'relative' && block.css('position') != 'absolute') {
+            if (!block.data('css-save-position'))
+                block.data('css-save-position', block.css('position'));
+            block.css('position', 'relative');
+        }
+
+        if (hc > he) {
+            e.css('top', parseInt(hc/2.0 - he/2.0));
+        }
+
+        if (hc < he) {
+            if (!block.data('css-save-min-height'))
+                block.data('css-save-min-height', block.css('min-height'));
+            block.css('min-height', he);
+        }
+
+    } else {
+
+        if (block.data('css-save-position'))
+            block.css('position', block.data('css-save-position'));
+
+        if (block.data('css-save-min-height'))
+            block.css('min-height', block.data('css-save-min-height'));
+
+        bx_loading_content(block, b);
+
+    }
+
+}
+
+
+/**
+ * Center content with floating blocks.
+ * sSel - jQuery selector of content to be centered
+ * sBlockSel - jquery selector of blocks
+ */
+function bx_center_content (sSel, sBlockStyle) {
+    var sId = 'id' + (new Date()).getTime();
+    $(sSel).wrap('<div id="'+sId+'"></div>');
+
+    var eCenter = $('#' + sId);
+    var iAll = $('#' + sId + ' ' + sBlockStyle).size();
+    var iWidthUnit = $('#' + sId + ' ' + sBlockStyle + ':first').outerWidth(true);
+    var iWidthContainer = eCenter.innerWidth();           
+    var iPerRow = parseInt(iWidthContainer/iWidthUnit);
+    var iLeft = (iWidthContainer - (iAll > iPerRow ? iPerRow * iWidthUnit : iAll * iWidthUnit)) / 2;
+    eCenter.css("padding-left", iLeft);
+}
+
+/**
+ * Show pointer popup with menu from URL.
+ * @param o - menu object name
+ * @param e - element to show popup at
+ * @param options - popup options
+ * @param vars - additional GET variables
+ */
+function bx_menu_popup (o, e, options, vars) {
+    var options = options || {};
+    var vars = vars || {};
+    var o = $.extend({}, $.fn.dolPopupDefaultOptions, options, {id: o, url: bx_append_url_params('menu.php', $.extend({o:o}, vars))});
+    if ('undefined' == typeof(e))
+        e = window;
+    $(e).dolPopupAjax(o);
+}
+
+/**
+ * Show pointer popup with menu from existing HTML.
+ * @param jSel - jQuery selector for html to show in popup
+ * @param e - element to show popup at
+ * @param options - popup options
+ */
+function bx_menu_popup_inline (jSel, e, options) {
+    var options = options || {};
+    var o = $.extend({}, $.fn.dolPopupDefaultOptions, options, {pointer:{el:$(e)}});
+    if ($(jSel + ':visible').length) 
+        $(jSel).dolPopupHide(); 
+    else 
+        $(jSel).dolPopup(o);
+}
+
+/**
+ * Show pointer popup with menu from existing HTML.
+ * @param jSel - jQuery selector for html to show in popup
+ * @param e - element to click to open/close slider
+ * @param sPosition - 'block' for sliding menu in blocks, 'site' - for sliding main menu
+ */
+function bx_menu_slide (jSel, e, sPosition) {
+    var options = options || {};
+    var eSlider = $(jSel);    
+
+    if ('undefined' == typeof(e))
+        e = eSlider.data('data-control-btn');
+
+    var eIcon = $(e).find('.sys-icon-a');    
+
+    var fPositionBlock = function () {
+        var eBlock = eSlider.parents('.bx-page-block-container');
+        eSlider.css({
+            position: 'absolute',
+            top: eBlock.find('.bx-db-header').outerHeight(true),
+            left: 0,
+            width: eBlock.width()
+        });
+    };
+
+    var fPositionSite = function () {
+        var eToolbar = $('#bx-toolbar');
+        eSlider.css({
+            position: 'fixed',
+            top: eToolbar.outerHeight(true),
+            left: 0
+        });
+    };
+
+    var fClose = function () {
+        if (eIcon.length)
+            (new Marka(eIcon[0])).set(eIcon.attr('data-icon-orig'));
+        eSlider.slideUp()
+    };
+
+    var fCloseAllOpened = function () {
+        $('.bx-sliding-menu-main:visible, .bx-popup-slide-wrapper:visible').each(function () {
+            bx_menu_slide('#' + this.id);
+        });
+    }
+
+    var fOpen = function () {
+        if (eIcon.length) {
+            eIcon.attr('data-icon-orig', eIcon.attr('data-icon'));
+            (new Marka(eIcon[0])).set('times');
+        }
+
+        if ('block' == sPosition)
+            fPositionBlock();
+        else
+            fPositionSite();
+
+        eSlider.slideDown();
+
+        eSlider.data('data-control-btn', e);
+    };    
+    
+    if ('undefined' == typeof(sPosition))
+        sPosition = 'block';
+
+    if ($(jSel + ':visible').length) {
+        fClose();
+        $(document).off('click.bx-sliding-menu');
+        $(window).off('resize.bx-sliding-menu');
+    } 
+    else {
+        fCloseAllOpened();
+        fOpen();
+        eSlider.find('a').each(function () {
+            $(this).off('click.bx-sliding-menu');
+            $(this).on('click.bx-sliding-menu', function () {
+                fClose();
+            });
+        });
+
+        setTimeout(function () {
+           
+            $(window).on('resize.bx-sliding-menu', function () {
+                fCloseAllOpened();
+            });
+ 
+            $(document).on('click.bx-sliding-menu', function (event) {
+                if ($(event.target).parents('.bx-sliding-menu-main, .bx-popup-slide-wrapper, .bx-db-header').length || $(event.target).filter('.bx-sliding-menu-main, .bx-popup-slide-wrapper, .bx-db-header').length || e.isSameNode(event.target))
+                    event.stopPropagation();
+                else
+                    fCloseAllOpened();
+            });
+
+        }, 10);
+    }
+}
+
+/**
+ * Set ACL level for specified profile
+ * @param iProfileId - profile id to set acl level for
+ * @param iAclLevel - acl level id to assign to a given rofile
+ */
+function bx_set_acl_level (iProfileId, iAclLevel, mixedLoadingElement) {
+
+    if ('undefined' != typeof(mixedLoadingElement))
+        bx_loading($(mixedLoadingElement), true);
+
+    $.post(sUrlRoot + 'menu.php', {o:'sys_set_acl_level', 'profile_id': iProfileId, 'acl_level_id': iAclLevel}, function(data) {
+
+        if ('undefined' != typeof(mixedLoadingElement))
+            bx_loading($(mixedLoadingElement), false);
+
+        if (data.length) {
+            alert(data);
+        } else if ($(mixedLoadingElement).hasClass('bx-popup-applied')) {
+            $(mixedLoadingElement).dolPopupHide().remove();
+        }
+    });
+}
+
+function validateLoginForm(eForm) {
+    return true;
+}
+
+/**
+ * convert <time> tags to human readable format
+ * @param sLang - use sLang localization
+ * @param isAutoupdate - for internal usage only
+ */
+function bx_time(sLang, isAutoupdate, sRootSel) {
+    if ('undefined' == typeof(sRootSel))
+        sRootSel = 'body';
+    var iAutoupdate = 22*60*60; // autoupdate time in realtime if less than 22 hours
+    var sSel = 'time';
+
+    if ('undefined' == typeof(sLang)) {
+        sLang = glBxTimeLang;
+    } else {
+        glBxTimeLang = sLang;
+    }
+
+    if ('undefined' != typeof(isAutoupdate) && isAutoupdate)
+        sSel += '.bx-time-autoupdate';
+
+    $(sRootSel).find(sSel).each(function () {
+        var s;
+        var sTime = $(this).attr('datetime');
+        var iSecondsDiff = moment(sTime).unix() - moment().unix();
+        if (iSecondsDiff < 0)
+            iSecondsDiff = -iSecondsDiff;
+
+        if (iSecondsDiff < $(this).attr('data-bx-autoformat'))
+            s = moment(sTime).lang(sLang).fromNow(); // 'time ago' format
+        else
+            s = moment(sTime).lang(sLang).format($(this).attr('data-bx-format')); // custom format
+
+        if (iSecondsDiff < iAutoupdate)
+            $(this).addClass('bx-time-autoupdate');
+        else
+            $(this).removeClass('bx-time-autoupdate');
+
+        $(this).html(s);
+    });
+
+    if ($('time.bx-time-autoupdate').size()) {
+        setTimeout(function () {
+            bx_time(sLang, true);
+        }, 20000);
+    }
+}
+
+(function($) {
+    $.fn.bxTime = function() {
+        bx_time(undefined, undefined, this);
+        return this;
+    }; 
+} (jQuery));
+
+
+/**
+ * Perform connections AJAX request. 
+ * In case of error - it shows js alert with error message.
+ * In case of success - the page is reloaded.
+ * 
+ * @param sObj - connections object
+ * @param sAction - 'add', 'remove' or 'reject'
+ * @param iContentId - content id, initiator is always current logged in user
+ * @param bConfirm - show confirmation dialog
+ */
+function bx_conn_action(e, sObj, sAction, iContentId, bConfirm) {
+    if ('undefined' != typeof(bConfirm) && bConfirm && !confirm(_t('_are you sure?')))
+        return;
+                
+    var aParams = {
+        obj: sObj,
+        act: sAction,
+        id: iContentId
+    };
+    var fCallback = function (data) {
+        bx_loading_btn(e, 0);
+        if ('object' != typeof(data))
+            return;
+        if (data.err) {
+            alert(data.msg);
+        } else {
+            if (!loadDynamicBlockAuto(e))
+                location.reload();
+            else
+                $('#bx-popup-ajax-wrapper-bx_persons_view_actions_more').remove();
+        }
+    };
+
+    bx_loading_btn(e, 1);
+
+    $.ajax({
+        dataType: 'json',
+        url: sUrlRoot + 'conn.php',
+        data: aParams,
+        type: 'POST',
+        success: fCallback
+    });
+}
+
+function bx_append_url_params (sUrl, mixedParams) {
+    var sParams = sUrl.indexOf('?') == -1 ? '?' : '&';
+
+    if(mixedParams instanceof Array) {
+    	for(var i in mixedParams)
+            sParams += i + '=' + mixedParams[i] + '&';
+        sParams = sParams.substr(0, sParams.length-1);
+    }
+    else if(mixedParams instanceof Object) {
+    	$.each(mixedParams, function(sKey, sValue) {
+    		sParams += sKey + '=' + sValue + '&';
+    	});
+        sParams = sParams.substr(0, sParams.length-1);
+    }
+    else
+        sParams += mixedParams;
+
+    return sUrl + sParams;
+}
+
+function bx_search_on_type (e, n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults) {
+    if ('undefined' != typeof(e) && 13 == e.keyCode) {
+        $(sFormSel).find('input[name=live_search]').val(0);
+        $(sFormSel).submit();
+        return false;
+    }
+
+    if ('undefined' != typeof(glBxSearchTimeoutHandler) && glBxSearchTimeoutHandler)
+        clearTimeout(glBxSearchTimeoutHandler);
+
+    glBxSearchTimeoutHandler = setTimeout(function () {
+        bx_search (n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults);
+    }, 500);
+
+    return true;
+}
+
+function bx_search (n, sFormSel, sResultsContSel, sLoadingContSel, bSortResults) {
+    if ('undefined' == typeof(sLoadingContSel))
+        sLoadingContSel = sResultsContSel;
+    if ('undefined' == typeof(bSortResults))
+        bSortResults = false;
+
+    var sQuery = $('input', sFormSel).serialize();
+
+    bx_loading($(sLoadingContSel), true);
+    $.post(sUrlRoot + 'searchKeywordContent.php', sQuery, function(data) {
+        bx_loading($(sLoadingContSel), false);
+        if (bSortResults) {
+            var aSortedUnits = $(data).find(".bx-def-unit-live-search").toArray().sort(function (a, b) {
+                return b.getAttribute('data-ts') - a.getAttribute('data-ts');
+            });
+            data = '';
+            $.each(aSortedUnits.slice(0, n), function (i, e) {
+                data += e.outerHTML;
+            });
+        } 
+        $(sResultsContSel).html(data).bxTime();
+    });
+
+    return false;
+}
+
+function on_filter_apply(e, sInputId, sFilterName)
+{
+	var oRegExp = new RegExp("[&]{0,1}" + sFilterName + "=.*");
+    var s = ('' + document.location).replace(oRegExp, ''); // remove filter
+    s = s.replace(/page=\d+/, 'page=1'); // goto 1st page
+    if (e.checked && $('#' + sInputId).val().length > 2)
+        s += (-1 == s.indexOf('?') ? '?' : '&') + sFilterName + '=' + $('#' + sInputId).val(); // append filter
+    document.location = s;
+}
+
+function on_filter_key_up (e, sCheckboxId)
+{
+    if (13 == e.keyCode) {
+        $('#' + sCheckboxId).click();
+        return false;
+    }
+    else {
+        $('#' + sCheckboxId).removeAttr('checked');
+        return true;
+    }
+}
+
+function bx_activate_anim_icons(sColor)
+{
+    if ('undefined' == typeof(sColor))
+        sColor = glBxAnimIconColor;
+    else
+       glBxAnimIconColor = sColor;
+
+    $('.sys-icon-a').not('.marka').each(function () {
+        var e = $(this);
+        var m = new Marka(e.get(0)), r = e.attr('data-rotate'), c = e.attr('data-color');
+        m.set(e.attr('data-icon')).rotate(r ? r : 'down').color(c ? c : sColor);
+    });
+}

+ 516 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/js/jquery.dolPopup.js

@@ -0,0 +1,516 @@
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+(function($) {
+
+    $.fn.dolPopupDefaultOptions = {
+        closeOnOuterClick: true,
+        closeElement: '.bx-popup-element-close', // link to element which will close popup
+        position: 'centered', // | 'absolute' | 'fixed' | event | element,
+        fog: {color: '#fff', opacity: .7}, // {color, opacity},
+        pointer: false, // {el:(string_id|jquery_object), align: (left|right|center)},
+        left: 0, // only for fixed or absolute
+        top: 0, // only for fixed
+        onBeforeShow: function () {},
+        onShow: function () {},
+        onBeforeHide: function () {},
+        onHide: function () {},
+        speed: 200
+    }; 
+
+    $.fn.dolPopupDefaultPointerOptions = {
+        align: 'right',
+        offset: '0 0',
+        offset_pointer: '0 0'
+    }; 
+
+    function _getScrollbarWidth () {
+        if ($(document.body).height() <= $(window).height()) {
+            return 0;
+        }
+
+        var outer = document.createElement("div"),
+            inner = document.createElement("div"),
+            widthNoScroll,
+            widthWithScroll;
+
+        outer.style.visibility = "hidden";
+        outer.style.width = "100px";
+        document.body.appendChild(outer);
+
+        widthNoScroll = outer.offsetWidth;
+
+        // Force scrollbars
+        outer.style.overflow = "scroll";
+
+        // Add innerdiv
+        inner.style.width = "100%";
+        outer.appendChild(inner);
+
+        widthWithScroll = inner.offsetWidth;
+
+        // Remove divs
+        outer.parentNode.removeChild(outer);
+
+        return widthNoScroll - widthWithScroll;
+    };
+
+    function _dolPopupLockScreen (bLock) {
+        var eBody = $(document.body),
+            eBodyHtml = $("html, body"),
+            iPaddingRight = parseInt(eBody.css("padding-right")) + ((bLock ? 1 : -1) * _getScrollbarWidth());
+
+        eBody.css("padding-right", iPaddingRight + "px");
+        bLock ? eBodyHtml.addClass('bx-popup-lock') : eBodyHtml.removeClass('bx-popup-lock');
+    };
+
+    $.fn.dolPopup = function(options) {
+        var options = options || {};
+        var o = $.extend({}, $.fn.dolPopupDefaultOptions, options);
+
+        if (false != o.pointer) {
+            o.fog = false;
+            o.pointer = $.extend({}, $.fn.dolPopupDefaultPointerOptions, $(document).data('bx-popup-options') ? $(document).data('bx-popup-options') : {}, o.pointer);
+        }
+
+        if (o.fog && !$('#bx-popup-fog').length) {
+            $('<div id="bx-popup-fog" class="bx-def-z-index-overlay" style="display: none;">&nbsp;</div>')
+                .prependTo('body');
+        }
+
+        $('#bx-popup-fog').css({
+            position: 'fixed',
+            top: 0,
+            left: 0,
+            width: $(window).width(),
+            height: $(window).height(),
+            opacity: o.fog.opacity,
+            backgroundColor: o.fog.color,
+            '-webkit-backface-visibility': 'hidden'
+        });
+        
+        $(window).on('resize.popupFog', function () {
+            $('#bx-popup-fog').css({
+                width: $(window).width(),
+                height: $(window).height()
+            });
+        });
+
+        return this.each(function() {
+            var $el = $(this);
+
+            $el.addClass('bx-def-z-index-modal');
+
+            // element must have id
+            if (!$el.attr('id'))
+                return false;
+
+            if ($el.is(":visible")) { // if popup is shown - hide it
+                $el.dolPopupHide();
+                return true;
+            } 
+
+            if (false != o.pointer) // all popups without pointer are responsive
+                $el.removeClass('bx-popup-responsive').addClass('bx-popup-with-pointer');
+
+            // default style for correct positioning
+            $el.css({
+                display: 'block',
+                visibility: 'hidden',
+                position: 'absolute',
+                top: 0,
+                left: 0
+            });
+
+            $el.detach().appendTo('body');
+
+
+            setTimeout(function() { // timeout is needed for some browsers to render element
+
+                if (o.fog)
+                    _dolPopupLockScreen(true);
+
+                // remove any transitions before setting popup position
+                $el.removeClass('bx-popup-transitions bx-popup-inactive'); 
+
+                // set popup position
+                $el._dolPopupSetPosition(o);
+
+                if (!$el.hasClass('bx-popup-applied')) { // do this only once
+                    $el.addClass('bx-popup-applied');
+
+                    // save options for element
+                    $el.data('bx-popup-options', o);
+
+                    // attach event for "close element"
+                    if (o.closeElement) {
+                        $(o.closeElement, $el)
+                            .css('cursor', 'hand')
+                            .click(function() {
+                                $el.dolPopupHide();
+                            });
+                    }
+
+                    // attach event for outer click checking
+                    if (o.closeOnOuterClick) {
+                        var fCallback = function(e) {
+                            if ($el.hasClass('bx-popup-applied') && $el.is(':visible')) {
+                                if ($(e.target).parents('#' + $el.attr('id')).length == 0) {
+                                    $el.dolPopupHide();
+                                }
+                            }
+
+                            return true;
+                        }
+
+                        $(document).on({
+                            click: fCallback,
+                            touchend: fCallback
+                        });
+                    }
+                }
+
+                o.onBeforeShow();
+
+                // transition effect
+                $el.addClass('bx-popup-inactive');
+                setTimeout(function () {
+                    $el.addClass('bx-popup-transitions bx-popup-active');
+                }, 10);
+    
+                // show popup
+                $el.css({display: 'block', visibility: 'visible'});
+                if (o.fog) {
+                    if (o.speed > 0)
+                        $('#bx-popup-fog').fadeIn(o.speed);
+                    else
+                        $('#bx-popup-fog').show();
+                }
+                
+                setTimeout(function () {
+                    o.onShow();                    
+                    $el.find('input[type=text]').focus(); // put cursor to the first input element
+                }, o.speed);
+
+            }, 100);
+        });
+    };
+
+    $.fn.dolPopupHide = function(options) { 
+
+        if ('undefined' == typeof(options) || 'object' != typeof(options))
+            options = {};
+
+        return this.each(function() {
+            var $el = $(this);
+
+            if (!$el.hasClass('bx-popup-applied'))
+                return false;
+
+            if (!$el.is(':visible') || 'hidden' == $el.css('visibility') || 'none' == $el.css('display'))
+                return false;            
+
+            var o = $.extend({}, $el.data('bx-popup-options'), options);
+
+            if (!o)
+                return false;
+
+            $(window).off('resize.popupWindow');
+            $(window).off('resize.popupPointer');
+            $(window).off('resize.popupFog');
+
+            o.onBeforeHide();
+
+            $el.removeClass('bx-popup-active').addClass('bx-popup-inactive');
+
+            if (o.speed > 0) {
+
+                if (o.fog)
+                    $('#bx-popup-fog').fadeOut(o.speed);
+
+                setTimeout(function () {
+                    $el.hide();
+                    o.onHide();
+                }, o.speed);
+
+            } else {
+
+                if (o.fog)
+                    $('#bx-popup-fog').hide();
+
+                $el.hide(o.onHide);
+            }
+            
+            _dolPopupLockScreen(false);
+        });
+    };
+
+
+    $.fn.dolPopupAjax = function(options) { 
+        
+        if ('undefined' == typeof(options) || 'object' != typeof(options) || 'undefined' == typeof(options.url))
+            return;
+
+        if ('undefined' == typeof(options.container))
+            options.container = '.bx-popup-content-wrapped';
+
+        var bx_menu_on = function (e, b) {
+            var li = $(e).parents('li:first');   
+            if (!li.length)
+                return;
+            if (b) {
+                var ul = $(e).parents('ul:first');   
+                ul.find('li').removeClass('bx-menu-tab-active');
+                li.addClass('bx-menu-tab-active');
+            } else {
+                li.removeClass('bx-menu-tab-active');
+            }
+        }
+
+        var bx_menu_is_on = function (e) {    
+            var li = $(e).parents('li:first');   
+            if (!li.length)
+                return false;
+            return li.hasClass('bx-menu-tab-active');
+        }
+
+        return this.each(function() {
+            var e = $(this);
+
+            // get id
+            var sPopupId = '';
+            var sIdPrefix = 'bx-popup-ajax-wrapper-';
+            if(typeof(e.attr('bx-popup-id')) != 'undefined')
+            	sPopupId = sIdPrefix + e.attr('bx-popup-id');
+            else if(typeof(options.id) != 'undefined')
+            	switch(typeof(options.id)) {
+            		case 'string':
+            			sPopupId = sIdPrefix + options.id;
+            			break;
+
+            		case 'object':
+            			sPopupId = typeof(options.id.force) != 'undefined' && options.id.force ? options.id.value : sIdPrefix + options.id.value;
+            			break;
+            	}
+            else
+            	sPopupId = sIdPrefix + parseInt(2147483647 * Math.random());
+
+            var oPointerOptions = $.isWindow(e[0]) ? false : $.extend({}, {el:$(e), align:'center'}, options.pointer);
+            if ($('#' + sPopupId + ':visible').length) { // if popup exists and is shown - hide it
+                
+                $('#' + sPopupId).dolPopupHide();
+
+            } else if ($('#' + sPopupId).length) { // if popup exists but not shown - unhide it
+
+                if (!$.isWindow(e[0]))
+                    bx_menu_on(e, true);
+
+                $('#' + sPopupId).dolPopup($.extend({}, options, {
+                    pointer: oPointerOptions,
+                    onHide: function () {
+                        if (!$.isWindow(e[0]))
+                            bx_menu_on(e, false);
+                    }
+                }));
+
+            } else { // if popup doesn't exists - create new one from provided url
+
+                if (!$.isWindow(e[0]))
+                    bx_menu_on(e, true);
+
+                $('<div id="' + sPopupId + '" style="display:none;">' + $('#bx-popup-loading').html() + '</div>').appendTo('body').find(options.container).hide();
+
+                $('#' + sPopupId).addClass($('#bx-popup-loading').attr('class'));
+
+                var oLoading = $('#' + sPopupId + ' .bx-popup-loading-wrapped');
+                bx_loading_content(oLoading, true, true);
+
+                $('#' + sPopupId).dolPopup($.extend({}, options, {
+                    pointer: oPointerOptions,
+                    onHide: function () {
+                        if (!$.isWindow(e[0]))
+                            bx_menu_on(e, false);
+                    }
+                }));
+
+                var fOnLoad = function() {
+                	bx_loading_content(oLoading, false);
+
+                	$('#' + sPopupId + ' ' + options.container).bxTime().show();
+
+					$('#' + sPopupId)._dolPopupSetPosition({
+						pointer: oPointerOptions
+					});
+                };
+
+                var sUrl = (options.url.indexOf('http://') == 0 || options.url.indexOf('https://') == 0 || options.url.indexOf('/') == 0 ? '' : sUrlRoot) + options.url;
+
+                $('#' + sPopupId).find(options.container).load(sUrl, function () {
+                	if($('#' + sPopupId).find('img').length > 0)
+                		$('#' + sPopupId).find('img').load(fOnLoad);
+                	else
+                		fOnLoad();
+                });
+            }
+        });
+    };
+
+
+    $.fn.dolPopupImage = function(sUrl, sLoadingElement) {
+
+        return this.each(function() {
+
+            var ePopup = $(this);
+            var k = .95,
+                w = $(window).width(),
+                h = $(window).height(),
+                eImg = new Image();
+
+            if (!ePopup.find('img').size())
+                return;
+
+            ePopup.filter('.bx-popup-wrapper').addClass('bx-popup-image-wrapper');
+
+            eImg.src = sUrl;
+
+            if ('undefined' != typeof(sLoadingElement))
+                bx_loading (sLoadingElement, true);
+
+            $(eImg).on('load', function () {
+
+                if ('undefined' != typeof(sLoadingElement))
+                    bx_loading (sLoadingElement, false);
+
+                ePopup.find('img').attr('src', sUrl);
+
+                if ('undefined' != typeof(window.matchMedia) && window.matchMedia("(max-width:720px)").matches)
+                    k = 1.0;
+
+                // fit image into the browser window
+                if (eImg.width < w * k && eImg.height < h * k) {
+                    ePopup.find('img').css({
+                        width: 'auto',
+                        height: 'auto'
+                    });
+                }
+                else if ((w * k - eImg.width) < (h * k - eImg.height)) {
+                    ePopup.find('img').css({
+                        width: '' + (eImg.width > w * k ? parseInt(w * k) : eImg.width) + 'px',
+                        height: 'auto'
+                    }).addClass('bx-constrain-width');
+                } else {
+                    ePopup.find('img').css({
+                        width: 'auto',
+                        height: '' + (eImg.height > h * k ? parseInt(h * k) : eImg.height) + 'px'
+                    }).addClass('bx-constrain-height');
+                }
+
+                // show popup
+                ePopup.dolPopup();
+
+                // hide popup upon clicking/tapping on the image
+                var fCallback = function (e) {
+                    e.stopPropagation();
+                    ePopup.dolPopupHide();
+                };
+                ePopup.find('img').on({
+                    click: fCallback,
+                    touchend: fCallback
+                });
+
+            });
+        });
+    };
+
+    $.fn._dolPopupSetPosition = function(options) {
+
+        var o = $.extend({}, $.fn.dolPopupDefaultOptions, options);
+
+        if (undefined != o.pointer && false != o.pointer)
+            o.pointer = $.extend({}, $.fn.dolPopupDefaultPointerOptions, $(document).data('bx-popup-options') ? $(document).data('bx-popup-options') : {}, o.pointer);
+
+        return this.each(function() {
+            var $el = $(this);
+            
+            if (o.pointer != false) {                
+                
+                var yOffset = 3;
+                var ePointAt = 'string' == o.pointer.el ? $(o.pointer.el) : o.pointer.el;
+                if (!ePointAt)
+                    ePointAt = $('body');
+
+                var aOffset = ('' + o.pointer.offset).split(' ', 2);
+                var aOffsetPointer = ('' + o.pointer.offset_pointer).split(' ', 2);
+                if (undefined == aOffset[0] || undefined == aOffset[1])
+                    aOffset = [0, 0];
+                if (undefined == aOffsetPointer[0] || undefined == aOffsetPointer[1])
+                    aOffsetPointer = [0, 0];
+
+                $el.position({
+                    of: ePointAt,
+                    my: o.pointer.align + '+' + parseInt(aOffset[0]) + ' top+' + yOffset + '+' + parseInt(aOffset[1] - 0),
+                    at: o.pointer.align + ' bottom',
+                    collision: 'flip none'
+                });
+
+                $el.find('.bx-popup-box-pointer').css('display', 'block').position({
+                    of: ePointAt,
+                    my: 'center+' + (parseInt(aOffsetPointer[0]) + parseInt(aOffset[0])) + ' top+' + yOffset + '+' + (parseInt(aOffsetPointer[1]) + parseInt(aOffset[1]) + 0),
+                    at: 'center bottom'
+                });
+
+                $(window).on('resize.popupPointer', function() {
+                    $el.position({
+                        of: ePointAt,
+                        my: o.pointer.align + '+' + parseInt(aOffset[0]) + ' top+' + yOffset + '+' + parseInt(aOffset[1]),
+                        at: o.pointer.align + ' bottom',
+                        collision: 'flip none'
+                    });
+                });
+
+            } else if (o.position == 'fixed' || o.position == 'absolute') {
+
+                $el.css({
+                    position: o.position,
+                    left: o.left,
+                    top: o.top
+                });
+
+            } else if (o.position == 'centered') {
+
+                $el.position({
+                    of: window,
+                    my: 'center center+' + ($el.outerHeight() > $(window).height() ? parseInt(($el.outerHeight() - $(window).height()) / 2) : '0'),
+                    at: 'center center',
+                    collision: 'none none'
+                });
+
+                // attach window resize event
+                $(window).on('resize.popupWindow', function() {
+
+                    $el.position({
+                        of: window,
+                        my: 'center center+' + ($el.outerHeight() > $(window).height() ? parseInt(($el.outerHeight() - $(window).height()) / 2) : '0'),
+                        at: 'center center',
+                        collision: 'none none'
+                    });
+                });
+
+            } else if (typeof o.position == 'object') {
+
+                $el.position({
+                    of: o.position,
+                    my: 'center top',
+                    at: 'center bottom',
+                    collision: 'flip flip'
+                });
+
+            }
+        });
+    };
+
+})(jQuery);
+

+ 12 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/inc/version.inc.php

@@ -0,0 +1,12 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+define('BX_DOL_VERSION', '8.0.0-A8'); ///< Dolphin system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 19 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/live_updates.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+bx_import('BxDolLiveUpdates');
+$oLiveUpdates = BxDolLiveUpdates::getInstance();
+
+if($oLiveUpdates) {
+    $aResult = $oLiveUpdates->perform();
+
+    header('Content-type: text/html; charset=utf-8');
+	echo json_encode($aResult);
+}

+ 57 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralConfig.php

@@ -0,0 +1,57 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxDolModuleConfig');
+
+class BxBaseModGeneralConfig extends BxDolModuleConfig
+{
+    public $CNF;
+
+    protected $_aPrefixes;
+    protected $_aJsClass;
+    protected $_aJsObjects;
+    protected $_aGridObjects;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_aPrefixes = array();
+        $this->_aJsClass = array();
+        $this->_aJsObjects = array();
+        $this->_aGridObjects = array();
+    }
+
+	public function getPrefix($sType = '')
+    {
+    	if(empty($sType))
+            return $this->_aPrefixes;
+
+        return isset($this->_aPrefixes[$sType]) ? $this->_aPrefixes[$sType] : '';
+    }
+
+	public function getJsClass($sType)
+    {
+        return isset($this->_aJsClass[$sType]) ? $this->_aJsClass[$sType] : '';
+    }
+
+    public function getJsObject($sType)
+    {
+		return isset($this->_aJsObjects[$sType]) ? $this->_aJsObjects[$sType] : '';
+    }
+
+    public function getGridObject($sType)
+    {
+		return isset($this->_aGridObjects[$sType]) ? $this->_aGridObjects[$sType] : '';
+    }
+}
+
+/** @} */

+ 223 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralFormsEntryHelper.php

@@ -0,0 +1,223 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxDolProfileForms');
+bx_import('BxDolForm');
+
+/**
+ * Entry forms helper functions
+ */
+class BxBaseModGeneralFormsEntryHelper extends BxDolProfileForms
+{
+    protected $_oModule;
+
+    public function __construct($oModule)
+    {
+        parent::__construct();
+        $this->_oModule = $oModule;
+    }
+
+    public function addDataForm ()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // check access
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->_oModule->checkAllowedAdd()))
+            return MsgBox($sMsg);
+
+        // check and display form
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oModule->_oTemplate);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $oForm->initChecker();
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        // insert data into database
+        $aValsToAdd = array ();
+        $iContentId = $oForm->insert ($aValsToAdd);
+        if (!$iContentId) {
+            if (!$oForm->isValid())
+                return $oForm->getCode();
+            else
+                return MsgBox(_t('_sys_txt_error_entry_creation'));
+        }
+
+        $sResult = $this->onDataAddAfter ($iContentId);
+        if ($sResult)
+            return $sResult;
+
+        // perform action
+        $this->_oModule->checkAllowedAdd(true);
+
+        // redirect
+        $this->_redirectAndExit('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId);
+    }
+
+    public function editDataForm ($iContentId, $sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'];
+
+        // 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->checkAllowedEdit($aContentInfo)))
+            return MsgBox($sMsg);
+
+        // check and display form
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $aSpecificValues = array();
+        bx_import('BxDolMetatags');
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $aSpecificValues = $oMetatags->locationGet($iContentId, $CNF['FIELD_LOCATION_PREFIX']);
+        }
+        $oForm->initChecker($aContentInfo, $aSpecificValues);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        // update data in the DB
+        $aTrackTextFieldsChanges = null;
+
+        $this->onDataEditBefore ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $aTrackTextFieldsChanges);
+
+        if (!$oForm->update ($aContentInfo[$CNF['FIELD_ID']], array(), $aTrackTextFieldsChanges)) {
+            if (!$oForm->isValid())
+                return $oForm->getCode();
+            else
+                return MsgBox(_t('_sys_txt_error_entry_update'));
+        }
+
+        $sResult = $this->onDataEditAfter ($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $aTrackTextFieldsChanges, $oProfile);
+        if ($sResult)
+            return $sResult;
+
+        // perform action
+        $this->_oModule->checkAllowedEdit($aContentInfo, true);
+
+        // redirect
+        $this->_redirectAndExit('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function deleteDataForm ($iContentId, $sDisplay = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_DELETE'];
+
+        // 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->checkAllowedDelete($aContentInfo)))
+            return MsgBox($sMsg);
+
+        // check and display form
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $oForm->initChecker($aContentInfo);
+
+        if (!$oForm->isSubmittedAndValid())
+            return $oForm->getCode();
+
+        if ($sError = $this->deleteData($aContentInfo[$CNF['FIELD_ID']], $aContentInfo, $oProfile, $oForm))
+            return MsgBox($sError);
+
+        // perform action
+        $this->_oModule->checkAllowedDelete($aContentInfo, true);
+
+        // redirect
+        bx_import('BxDolPermalinks');
+        $this->_redirectAndExit($CNF['URL_HOME'], true, array(
+            'account_id' => $oProfile->getAccountId(),
+            'profile_id' => $oProfile->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 = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_DELETE'], $this->_oModule->_oTemplate);
+
+        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;
+
+        if (false === $sDisplay)
+            $sDisplay = $CNF['OBJECT_FORM_ENTRY_DISPLAY_VIEW'];
+
+        // 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);
+
+        // get form
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $sDisplay, $this->_oModule->_oTemplate);
+        if (!$oForm)
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        // display profile
+        $oForm->initChecker($aContentInfo);
+        return $oForm->getCode();
+    }
+
+}
+
+/** @} */

+ 143 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralGridAdministration.php

@@ -0,0 +1,143 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     DolphinModules
+ * @{
+ */
+
+bx_import('BxDolModule');
+bx_import('BxTemplGrid');
+
+class BxBaseModGeneralGridAdministration extends BxTemplGrid
+{
+	protected $MODULE;
+	protected $_oModule;
+
+	protected $_sManageType;
+	protected $_sParamsDivider;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+        
+        $this->_sManageType = 'administration';
+        $this->_sParamsDivider = '#-#';
+    }
+
+	public function performActionDelete($aParams = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	//TODO: remove this check when 'delete with content' feature will be realized in onDelete method.
+    	if(isset($aParams['with_content']) && $aParams['with_content'] === true) {
+			$this->_echoResultJson(array('msg' => 'TODO: delete with content'));
+	    	return;
+    	}
+
+        $iAffected = 0;
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $this->_echoResultJson(array());
+            exit;
+        }
+
+        bx_import('FormsEntryHelper', $this->_oModule->_aModule);
+        $sClass = $this->_oModule->_oConfig->getClassPrefix() . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this->_oModule);
+
+        $aIdsAffected = array ();
+        foreach($aIds as $iId) {
+			$aContentInfo = $this->_oModule->_oDb->getContentInfoById($iId);
+	    	if($this->_oModule->checkAllowedDelete($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+	    		continue;
+
+        	if($oFormsHelper->deleteData($iId) != '')
+                continue;
+
+			if(!$this->_onDelete($iId, $aParams))
+				continue;
+
+			$this->_oModule->checkAllowedDelete($aContentInfo, true);
+
+            $aIdsAffected[] = $iId;
+            $iAffected++;
+        }
+
+        $this->_echoResultJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t($CNF['T']['grid_action_err_delete'])));
+    }
+
+    protected function _getActionSettings($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+    	$sJsObject = $this->_oModule->_oConfig->getJsObject('manage_tools');
+    	$sMenuName = $this->_oModule->_oConfig->CNF['OBJECT_MENU_MANAGE_TOOLS'];
+
+    	bx_import('BxDolMenu');
+    	$sMenu = BxDolMenu::getObjectInstance($sMenuName)->getCode();
+    	if(empty($sMenu))
+    		return '';
+
+    	$a['attr'] = array_merge($a['attr'], array(
+    		"bx-popup-id" => $sMenuName . "-" . $aRow['id'],
+    		"onclick" => "$(this).off('click'); " . $sJsObject . ".onClickSettings('" . $sMenuName . "', this);"
+    	));
+
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getFilterSelectOne($sFilterName, $sFilterValue, $aFilterValues)
+    {
+        if(empty($sFilterName) || empty($aFilterValues))
+            return '';
+
+		$CNF = &$this->_oModule->_oConfig->CNF;
+		$sJsObject = $this->_oModule->_oConfig->getJsObject('manage_tools');
+
+		foreach($aFilterValues as $sKey => $sValue)
+			$aFilterValues[$sKey] = _t($sValue);
+
+        $aInputModules = array(
+            'type' => 'select',
+            'name' => $sFilterName,
+            'attrs' => array(
+                'id' => 'bx-grid-' . $sFilterName . '-' . $this->_sObject,
+                'onChange' => 'javascript:' . $sJsObject . '.onChangeFilter(this)'
+            ),
+            'value' => $sFilterValue,
+            'values' => array_merge(array('' => _t($CNF['T']['filter_item_select_one_' . $sFilterName])), $aFilterValues)
+        );
+
+        bx_import('BxTemplFormView');
+        $oForm = new BxTemplFormView(array());
+        return $oForm->genRow($aInputModules);
+    }
+
+	protected function _getSearchInput()
+    {
+        $sJsObject = $this->_oModule->_oConfig->getJsObject('manage_tools');
+
+        $aInputSearch = array(
+            'type' => 'text',
+            'name' => 'search',
+            'attrs' => array(
+                'id' => 'bx-grid-search-' . $this->_sObject,
+                'onKeyup' => 'javascript:$(this).off(\'keyup\'); ' . $sJsObject . '.onChangeFilter(this)'
+            )
+        );
+
+		bx_import('BxTemplFormView');
+        $oForm = new BxTemplFormView(array());
+        return $oForm->genRow($aInputSearch);
+    }
+
+    protected function _onDelete($iId, $aParams = array())
+    {
+    	return true;
+    }
+}
+
+/** @} */

+ 38 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralMenuManageTools.php

@@ -0,0 +1,38 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxTemplMenu');
+
+/**
+ * Manage tools submenu
+ */
+class BxBaseModGeneralMenuManageTools extends BxTemplMenu
+{
+	protected $_oModule;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        $iContentId = 0;
+        if(bx_get('content_id') !== false)
+        	$iContentId = (int)bx_get('content_id');
+
+		$this->addMarkers(array(
+			'js_object' => $this->_oModule->_oConfig->getJsObject('manage_tools'),
+			'content_id' => $iContentId
+		));
+    }
+}
+
+/** @} */

+ 307 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralModule.php

@@ -0,0 +1,307 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import ('BxDolModule');
+
+/**
+ * Base module class.
+ */
+class BxBaseModGeneralModule extends BxDolModule
+{
+    protected $_iProfileId;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+        $this->_iProfileId = bx_get_logged_profile_id();
+    }
+
+    // ====== ACTIONS METHODS
+
+    public function actionRss ($sMode = '')
+    {
+        $aArgs = func_get_args();
+        $sMode = array_shift($aArgs);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse())) {
+            $this->_oTemplate->displayAccessDenied ($sMsg);
+            exit;
+        }
+
+        $aParams = $this->_buildRssParams($sMode, $aArgs);
+
+        bx_import ('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass($sMode, $aParams);
+
+        if ($o->isError) {
+            $this->_oTemplate->displayPageNotFound ();
+            exit;
+        }
+
+        $o->outputRSS();
+        exit;
+    }
+
+    // ====== SERVICE METHODS
+
+	public function serviceManageTools($sType = 'common')
+    {
+    	bx_import('BxDolGrid');
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->getGridObject($sType));
+        if(!$oGrid)
+            return '';
+
+		$CNF = &$this->_oConfig->CNF;
+
+		bx_import('BxDolAcl');
+		$oAcl = BxDolAcl::getInstance();
+
+		$sMenu = '';
+		if($oAcl->isMemberLevelInSet(192)) {
+			bx_import('BxDolPermalinks');
+			$oPermalink = BxDolPermalinks::getInstance();
+
+			$aMenuItems = array(
+				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($oAcl->isMemberLevelInSet(64))
+				$aMenuItems[] = array('id' => 'manage-moderation', 'name' => 'manage-moderation', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_MODERATION']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_all']), 'active' => 1);
+			if($oAcl->isMemberLevelInSet(128))
+				$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);
+
+			bx_import('BxTemplMenu');
+            $oMenu = new BxTemplMenu(array(
+            	'template' => 'menu_vertical.html', 
+            	'menu_items' => $aMenuItems
+            ), $this->_oTemplate);
+            $oMenu->setSelected($this->_aModule['name'], 'manage-' . $sType);
+            $sMenu = $oMenu->getCode();
+		}
+
+		bx_import('BxTemplMenu');
+		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;
+    }
+
+    /**
+     * Add entry form
+     * @return HTML string
+     */
+    public function serviceEntityCreate ()
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->addDataForm();
+    }
+
+    public function serviceEntityEdit ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId);
+    }
+
+    public function serviceEntityDelete ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('deleteDataForm', $iContentId);
+    }
+
+    public function serviceEntityInfo ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId);
+    }
+
+    /**
+     * Entry actions block
+     */
+    public function serviceEntityActions ($iContentId = 0)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        bx_import('BxTemplMenu');
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    // ====== PERMISSION METHODS
+
+    /**
+     * @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 ()
+    {
+        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)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // moderator and owner always have access
+        if ($aDataEntry[$CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_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'])) {
+            bx_import('BxDolPrivacy');
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+            if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']]))
+                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. 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 || $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 && $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($this->_iProfileId))
+            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;
+    }
+
+    // ====== PROTECTED METHODS
+
+    protected function _serviceBrowse ($sMode, $aParams = false, $iDesignBox = BX_DB_PADDING_DEF, $bDisplayEmptyMsg = false, $bAjaxPaginate = true)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse()))
+            return MsgBox($sMsg);
+
+        bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass($sMode, $aParams);
+
+        $o->setDesignBoxTemplateId($iDesignBox);
+        $o->setDisplayEmptyMsg($bDisplayEmptyMsg);
+        $o->setAjaxPaginate($bAjaxPaginate);
+
+        if ($o->isError)
+            return '';
+
+        if ($s = $o->processing())
+            return $s;
+        else
+            return '';
+    }
+
+    protected function _isModerator ($isPerformAction = false)
+    {
+        // check moderator ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'edit any entry', $this->getName(), $isPerformAction);
+        return $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    protected function _serviceEntityForm ($sFormMethod, $iContentId = 0, $sDisplay = false)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFormMethod((int)$iContentId, $sDisplay);
+    }
+
+    protected function _serviceTemplateFunc ($sFunc, $iContentId)
+    {
+        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;
+
+        return $this->_oTemplate->$sFunc($aContentInfo);
+    }
+}
+
+/** @} */

+ 46 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/classes/BxBaseModGeneralTemplate.php

@@ -0,0 +1,46 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxDolModuleTemplate');
+
+/**
+ * Module representation.
+ */
+class BxBaseModGeneralTemplate extends BxDolModuleTemplate
+{
+    protected $MODULE;
+
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+        $this->addCss ('main.css');
+    }
+
+	public function getJsCode($sType, $aParams = array(), $bWrap = true)
+    {
+        $sBaseUri = $this->_oConfig->getBaseUri();
+        $sJsClass = $this->_oConfig->getJsClass($sType);
+        $sJsObject = $this->_oConfig->getJsObject($sType);
+
+        $aParams = array_merge(array(
+            'sActionUri' => $sBaseUri,
+            'sActionUrl' => BX_DOL_URL_ROOT . $sBaseUri,
+            'sObjName' => $sJsObject,
+        	'aHtmlIds' => array(),
+            'oRequestParams' => array()
+        ), $aParams);
+        $sContent = "var " . $sJsObject . " = new " . $sJsClass . "(" . json_encode($aParams) . ");";
+
+        return !$bWrap ? $sContent : $this->_wrapInTagJsCode($sContent);
+    }
+}
+
+/** @} */

+ 15 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/general/template/css/manage_tools.css

@@ -0,0 +1,15 @@
+/*--- General ---*/
+div.bx-grid-header div.bx-form-element-wrapper {
+    position: relative;
+    float: left;
+    width: 165px;
+}
+
+/*--- Administration tools ---*/
+div.bx-grid-header div#bx-form-element-filter1 {
+    margin-right: 20px;
+}
+div.bx-grid-header div#bx-form-element-filter1,
+div.bx-grid-header div#bx-form-element-search {
+    margin-top: 0px;
+}

+ 148 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/notifications/classes/BxBaseModNotificationsConfig.php

@@ -0,0 +1,148 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseNotifications Base classes for Notifications like modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxBaseModGeneralConfig');
+
+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 $_iPrivacyViewDefault;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+		$this->_aPrefixes = array();
+		$this->_aObjects = array(
+			'alert' => $this->_sName,
+			'conn_subscriptions' => 'sys_profiles_subscriptions'
+		);
+
+        $this->_aHandlerDescriptor = array();
+        $this->_sHandlersMethod = '';
+        $this->_aHandlersHidden = array();
+        $this->_aHandlers = array();
+
+		$this->_aJsClass = array();
+		$this->_aJsObjects = array();
+
+		$this->_aPerPage = array();
+		$this->_aHtmlIds = array();
+
+		$this->_sAnimationEffect = 'fade';
+        $this->_iAnimationSpeed = 'slow';
+
+        $this->_iPrivacyViewDefault = 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 getObject($sType = '')
+    {
+    	if(empty($sType))
+            return $this->_aObjects;
+
+        return isset($this->_aObjects[$sType]) ? $this->_aObjects[$sType] : '';
+    }
+
+	public function getHandlerDescriptor()
+    {
+    	return $this->_aHandlerDescriptor;
+    }
+    public function getHandlersMethod()
+    {
+    	return $this->_sHandlersMethod;
+    }
+
+    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 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()
+    {
+        return $this->_iPrivacyViewDefault;
+    }
+}
+
+/** @} */

+ 62 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/notifications/classes/BxBaseModNotificationsTemplate.php

@@ -0,0 +1,62 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseNotifications Base classes for Notifications like modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxBaseModGeneralTemplate');
+
+class BxBaseModNotificationsTemplate extends BxBaseModGeneralTemplate
+{
+	function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+    }
+
+	public function getCssJs()
+    {
+        $this->addCss(array(
+            'view.css',
+            'view-media-tablet.css',
+            'view-media-desktop.css',
+        ));
+        $this->addJs(array(
+        	'jquery.anim.js',
+            'main.js',
+            'view.js',
+        ));
+    }
+
+	public function getJsCode($sType, $aParams = array(), $bWrap = true)
+    {
+        $oModule = $this->getModule();
+
+        $sBaseUri = $this->_oConfig->getBaseUri();
+        $sJsClass = $this->_oConfig->getJsClass($sType);
+        $sJsObject = $this->_oConfig->getJsObject($sType);
+
+        $aParams = array_merge(array(
+        	'iOwnerId' => $oModule->_iOwnerId,
+            'sAnimationEffect' => $this->_oConfig->getAnimationEffect(),
+            'iAnimationSpeed' => $this->_oConfig->getAnimationSpeed(),
+            'aHtmlIds' => $this->_oConfig->getHtmlIds($sType)
+        ), $aParams);
+
+        $this->getCssJs();
+        return parent::getJsCode($sType, $aParams, $bWrap);
+    }
+
+	protected function getModule()
+    {
+        $sName = $this->_oConfig->getName();
+        return BxDolModule::getInstance($sName);
+    }
+
+}
+
+/** @} */

+ 111 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php

@@ -0,0 +1,111 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxBaseModGeneralFormsEntryHelper');
+bx_import('BxDolProfile');
+
+/**
+ * Profile forms helper functions
+ */
+class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
+{
+    public function __construct($oModule)
+    {
+        parent::__construct($oModule);
+    }
+
+    protected function _getProfileAndContentData ($iContentId)
+    {
+        $aContentInfo = array();
+        $oProfile = false;
+
+        $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return array (false, false);
+
+        $oProfile = BxDolProfile::getInstance($aContentInfo['profile_id']);
+        if (!$oProfile) {
+            bx_import('BxDolProfileUndefined');
+            $oProfile = BxDolProfileUndefined::getInstance();
+        }
+
+        return array ($oProfile, $aContentInfo);
+    }
+
+    protected function onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile)
+    {
+        // delete profile
+        if (!$oProfile->delete($aContentInfo['profile_id']))
+            return _t('_sys_txt_error_entry_delete');
+
+        return '';
+    }
+
+    protected function onDataEditBefore ($iContentId, $aContentInfo, &$aTrackTextFieldsChanges)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        list ($oProfile, $aContentInfo) = $this->_getProfileAndContentData($iContentId);
+        $aProfileInfo = $oProfile->getInfo();
+
+        $isAutoApprove = getParam($CNF['PARAM_AUTOAPPROVAL']) ? true : false;
+        if (!$isAutoApprove && BX_PROFILE_STATUS_ACTIVE == $aProfileInfo['status'])
+            $aTrackTextFieldsChanges = array ();
+    }
+
+    protected function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aProfileInfo = $oProfile->getInfo();
+
+        // change profile to 'pending' only if profile is 'active'
+        $isAutoApprove = getParam($CNF['PARAM_AUTOAPPROVAL']) ? true : false;
+        if (!$isAutoApprove && BX_PROFILE_STATUS_ACTIVE == $aProfileInfo['status'] && !empty($aTrackTextFieldsChanges['changed_fields']))
+            $oProfile->disapprove(BX_PROFILE_ACTION_AUTO);
+
+        // create an alert
+        bx_alert($this->_oModule->getName(), 'edited', $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    protected function onDataAddAfter ($iContentId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        // add account and content association
+        $iProfileId = BxDolProfile::add(BX_PROFILE_ACTION_MANUAL, getLoggedId(), $iContentId, BX_PROFILE_STATUS_PENDING, $this->_oModule->getName());
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+
+        // approve profile if auto-approval is enabled and profile status is 'pending'
+        $sStatus = $oProfile->getStatus();
+        $isAutoApprove = getParam($CNF['PARAM_AUTOAPPROVAL']) ? true : false;
+        if ($sStatus == BX_PROFILE_STATUS_PENDING && $isAutoApprove)
+            $oProfile->approve(BX_PROFILE_ACTION_AUTO);
+
+        // set created profile some default membership
+        bx_import('BxDolAcl');
+        $iAclLevel = isAdmin() ? MEMBERSHIP_ID_ADMINISTRATOR : getParam($CNF['PARAM_DEFAULT_ACL_LEVEL']);
+        BxDolAcl::getInstance()->setMembership($iProfileId, $iAclLevel, 0, true);
+
+        // alert
+        bx_alert($this->_oModule->getName(), 'added', $iContentId);
+
+        // switch context to the created profile
+        bx_import('BxDolAccount');
+        $oAccount = BxDolAccount::getInstance();
+        $oAccount->updateProfileContext($iProfileId);
+
+        return '';
+    }
+
+}
+
+/** @} */

+ 168 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileGridAdministration.php

@@ -0,0 +1,168 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     DolphinModules
+ * 
+ * @{
+ */
+
+bx_import('BxBaseModGeneralGridAdministration');
+
+class BxBaseModProfileGridAdministration extends BxBaseModGeneralGridAdministration
+{
+	protected $_sFilter1Name;
+	protected $_sFilter1Value;
+	protected $_aFilter1Values;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_sFilter1Name = 'filter1';
+        $this->_aFilter1Values = array(
+			'active' => $CNF['T']['filter_item_active'],
+            'pending' => $CNF['T']['filter_item_pending'],
+            'suspended' => $CNF['T']['filter_item_suspended'],
+		);
+
+    	$sFilter1 = bx_get($this->_sFilter1Name);
+        if(!empty($sFilter1)) {
+            $this->_sFilter1Value = bx_process_input($sFilter1);
+            $this->_aQueryAppend[$this->_sFilter1Name] = $this->_sFilter1Value;
+        }
+    }
+
+    public function performActionSetAclLevel()
+    {
+    	bx_import('BxDolMenu');
+    	$oMenu = BxDolMenu::getObjectInstance('sys_set_acl_level');
+
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds) || !$oMenu) {
+            $this->_echoResultJson(array());
+            return;
+        }
+
+        $aIdsResult = array();
+        foreach($aIds as $iId) {
+        	$aContentInfo = $this->_oModule->_oDb->getContentInfoById($iId);
+	    	if($this->_oModule->checkAllowedSetMembership($aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+	    		continue;
+
+        	$aIdsResult[] = $this->_getProfileId($iId);
+        }
+
+        if(empty($aIdsResult)) {
+            $this->_echoResultJson(array());
+            return;
+        }
+
+		if(count($aIdsResult) == 1)
+			$aIdsResult = $aIdsResult[0];
+
+		$sContent = $this->_oTemplate->parseHtmlByName('bx_div.html', array(
+			'bx_repeat:attrs' => array(
+				array('key' => 'class', 'value' => 'bx-def-padding')
+			),
+			'content' => $oMenu->getCode($aIdsResult)
+		));
+
+    	bx_import('BxTemplFunctions');
+		$sContent = BxTemplFunctions::getInstance()->transBox($this->_oModule->_oConfig->getName() . 'set_acl_level_popup', $sContent);
+
+    	$this->_echoResultJson(array('popup' => $sContent), true);
+    }
+
+	public function performActionDeleteWithContent()
+    {
+    	$this->performActionDelete(array('with_content' => true));
+    }
+
+	protected function _switcherChecked2State($isChecked)
+    {
+        return $isChecked ? 'active' : 'suspended';
+    }
+
+    protected function _switcherState2Checked($mixedState)
+    {
+        return 'active' == $mixedState ? true : false;
+    }
+
+    protected function _enable ($mixedId, $isChecked)
+    {
+    	$iAction = BX_PROFILE_ACTION_MANUAL;
+    	$oProfile = $this->_getProfileObject($mixedId);
+    	return $isChecked ? $oProfile->activate($iAction) : $oProfile->suspend($iAction);
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(strpos($sFilter, $this->_sParamsDivider) !== false)
+            list($this->_sFilter1Value, $sFilter) = explode($this->_sParamsDivider, $sFilter);
+
+    	if(!empty($this->_sFilter1Value))
+        	$this->_aOptions['source'] .= $this->_oModule->_oDb->prepare(" AND `tp`.`status`=?", $this->_sFilter1Value);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    //--- Layout methods ---//
+	protected function _getFilterControls()
+    {
+        parent::_getFilterControls();
+
+        return  $this->_getFilterSelectOne($this->_sFilter1Name, $this->_sFilter1Value, $this->_aFilter1Values) . $this->_getSearchInput();
+    }
+
+    protected function _getCellFullname($mixedValue, $sKey, $aField, $aRow)
+    {
+    	$oProfile = $this->_getProfileObject($aRow['id']);
+
+        $mixedValue = $this->_oTemplate->parseHtmlByName('bx_a.html', array(
+            'href' => $oProfile->getUrl(),
+            'title' => $mixedValue,
+            'bx_repeat:attrs' => array(),
+            'content' => $mixedValue
+        ));
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellLastOnline($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(bx_time_js($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getProfileObject($iId)
+    {
+    	bx_import('BxDolProfile');
+    	$oProfile = BxDolProfile::getInstanceByContentAndType((int)$iId, $this->_oModule->_oConfig->getName());
+        if (!$oProfile) {
+            bx_import('BxDolProfileUndefined');
+            $oProfile = BxDolProfileUndefined::getInstance();
+        }
+        return $oProfile;
+    }
+
+    protected function _getProfileId($iId)
+    {
+    	return $this->_getProfileObject($iId)->id();
+    }
+
+    protected function _onDelete($iId, $aParams = array())
+    {
+    	if(isset($aParams['with_content']) && $aParams['with_content'] === true)	{
+			//TODO: delete content after profile deletion
+		}
+
+		return parent::_onDelete($iId, $aParams);
+    }
+    
+}
+
+/** @} */

+ 31 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileGridCommon.php

@@ -0,0 +1,31 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ * 
+ * @{
+ */
+
+bx_import('BxBaseModProfileGridAdministration');
+
+class BxBaseModProfileGridCommon extends BxBaseModProfileGridAdministration
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sManageType = 'common';
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+		$this->_aOptions['source'] .= $this->_oModule->_oDb->prepare(" AND `tp`.`account_id`=?", getLoggedId());
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+}
+
+/** @} */

+ 29 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileGridModeration.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     DolphinModules
+ * 
+ * @{
+ */
+
+bx_import('BxBaseModProfileGridAdministration');
+
+class BxBaseModProfileGridModeration extends BxBaseModProfileGridAdministration
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sManageType = 'moderation';
+    }
+
+	public function performActionDelete($bWithContent = false)
+    {
+        $this->_echoResultJson(array());
+    }
+}
+
+/** @} */

+ 25 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileMenuManageTools.php

@@ -0,0 +1,25 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxBaseModGeneralMenuManageTools');
+
+/**
+ * Manage tools submenu
+ */
+class BxBaseModProfileMenuManageTools extends BxBaseModGeneralMenuManageTools
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+    }
+}
+
+/** @} */

+ 375 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/profile/classes/BxBaseModProfileModule.php

@@ -0,0 +1,375 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import ('BxBaseModGeneralModule');
+bx_import ('BxDolAcl');
+
+/**
+ * Base class for profile modules.
+ */
+class BxBaseModProfileModule extends BxBaseModGeneralModule implements iBxDolProfileService
+{
+    protected $_iAccountId;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+        $this->_iAccountId = getLoggedId();
+    }
+
+    // ====== SERVICE METHODS
+	public function serviceGetMenuAddonManageTools()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'perofileStatus' => BX_PROFILE_STATUS_PENDING
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+	public function serviceGetMenuAddonManageToolsProfileStats()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'account_id' => getLoggedId(),
+        	'perofileStatus' => ''
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+    public function serviceGetMenuSetNameForMenuTrigger ($sMenuTriggerName)
+    {
+        if ('trigger_profile_view_submenu' == $sMenuTriggerName)
+            return $this->_oConfig->CNF['OBJECT_MENU_SUBMENU_VIEW_ENTRY'];
+        elseif ('trigger_profile_view_actions' == $sMenuTriggerName)
+            return $this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY'];
+        return '';
+    }
+
+    public function serviceProfilesSearch ($sTerm, $iLimit)
+    {
+        $aRet = array();
+        $a = $this->_oDb->searchByTerm($sTerm, $iLimit);
+        foreach ($a as $r)
+            $aRet[] = array ('label' => $this->serviceProfileName($r['content_id']), 'value' => $r['profile_id']);
+        return $aRet;
+    }
+
+    public function serviceProfileUnit ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('unit', $iContentId);
+    }
+
+    public function serviceProfileAvatar ($iContentId)
+    {
+        return $this->_serviceTemplateFunc('urlAvatar', $iContentId);
+    }
+
+    public function serviceProfileEditUrl ($iContentId)
+    {
+        bx_import('BxDolPermalinks');
+        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 $aContentInfo[$this->_oConfig->CNF['FIELD_NAME']];
+    }
+
+    public function serviceProfileUrl ($iContentId)
+    {
+        if (!$iContentId)
+            return false;
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+        $CNF = $this->_oConfig->CNF;
+        bx_import('BxDolPermalinks');
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function serviceBrowseRecentProfiles ($bDisplayEmptyMsg = false)
+    {
+        return $this->_serviceBrowse ('recent', false, BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+
+    public function serviceBrowseConnections ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iDesignBox = BX_DB_PADDING_DEF, $iProfileId2 = 0)
+    {
+        return $this->_serviceBrowse (
+            'connections',
+            array(
+                'object' => $sObjectConnections,
+                'type' => $sConnectionsType,
+                'mutual' => $iMutual,
+                'profile' => (int)$iProfileId,
+                'profile2' => (int)$iProfileId2),
+            $iDesignBox
+        );
+    }
+
+    public function serviceBrowseConnectionsQuick ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iProfileId2 = 0)
+    {
+        // get connections object
+        bx_import('BxDolConnection');
+        $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
+        $a = $oConnection->getConnectionsAsArray ($sConnectionsType, $iProfileId, $iProfileId2, $iMutual, (int)bx_get('start'), $iLimit + 1, BX_CONNECTIONS_ORDER_ADDED_DESC);
+        if (!$a)
+            return '';
+
+        // get paginate object
+        bx_import('BxTemplPaginate');
+        $oPaginate = new BxTemplPaginate(array(
+            'on_change_page' => "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}');",
+            'num' => count($a),
+            'per_page' => $iLimit,
+            'start' => $iStart,
+        ));
+
+        // remove last item from connection array, because we've got one more item for pagination calculations only
+        if (count($a) > $iLimit)
+            array_pop($a);
+
+        // get profiles HTML
+        bx_import('BxDolProfile');
+        $s = '';
+        foreach ($a as $iProfileId) {
+            if (!($o = BxDolProfile::getInstance($iProfileId))) {
+                continue;
+            }
+            $s .= $o->getUnit();
+        }
+
+        // return profiles + paginate
+        return $s . (!$iStart && $oPaginate->getNum() <= $iLimit ?  '' : $oPaginate->getSimplePaginate());
+    }
+
+    public function serviceEntityEditCover ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT_COVER']);
+    }
+
+    public function serviceProfileCover ($iContentId = 0)
+    {
+       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;
+
+        return $this->_oTemplate->cover($aContentInfo);
+    }
+
+    public function serviceProfileFriends ($iContentId = 0)
+    {
+        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;
+
+        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;
+    }
+
+    // ====== PERMISSION METHODS
+
+    /**
+     * @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
+        bx_import('BxDolProfile');
+        $oProfileAurhor = BxDolProfile::getInstance($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']]);
+        if ($oProfileAurhor->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
+        bx_import('BxDolProfile');
+        $oProfileAurhor = BxDolProfile::getInstance($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']]);
+        if ($oProfileAurhor->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)
+        bx_import('BxDolProfile');
+        $oProfileAurhor = BxDolProfile::getInstance($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']]);
+
+        $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
+        if ($oProfileAurhor->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)
+    {
+        bx_import('BxTemplMenu');
+        $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 checkAllowedFriendAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFriendRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', false, true, true))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, 'sys_profiles_friends', false, true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        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);
+    }
+
+    // ====== 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;
+
+        $oProfileAuthor = BxDolProfile::getInstance($aDataEntry[$CNF['FIELD_AUTHOR']]);
+        $oProfile = $oProfileAuthor ? BxDolProfile::getInstanceByContentTypeAccount($aDataEntry[$CNF['FIELD_ID']], $this->_aModule['name'], $oProfileAuthor->getAccountId()) : false;
+        if (!$oProfile || $oProfile->id() == $this->_iProfileId)
+            return _t('_sys_txt_access_denied');
+
+        bx_import('BxDolConnection');
+        $oConn = BxDolConnection::getObjectInstance($sObjConnection);
+        if ($isSwap)
+            $isConnected = $oConn->isConnected($oProfile->id(), $this->_iProfileId, $isMutual);
+        else
+            $isConnected = $oConn->isConnected($this->_iProfileId, $oProfile->id(), $isMutual);
+
+        if ($isInvertResult)
+            $isConnected = !$isConnected;
+
+        return $isConnected ? _t('_sys_txt_access_denied') : CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    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;
+    }
+}
+
+/** @} */

+ 131 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextGridAdministration.php

@@ -0,0 +1,131 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ * 
+ * @{
+ */
+
+bx_import('BxBaseModGeneralGridAdministration');
+
+class BxBaseModTextGridAdministration extends BxBaseModGeneralGridAdministration
+{
+	protected $_sFilter1Name;
+	protected $_sFilter1Value;
+	protected $_aFilter1Values;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_sFilter1Name = 'filter1';
+        $this->_aFilter1Values = array(
+        	'active' => $CNF['T']['filter_item_active'],
+            'hidden' => $CNF['T']['filter_item_hidden'],
+		);
+        
+    	$sFilter1 = bx_get($this->_sFilter1Name);
+        if(!empty($sFilter1)) {
+            $this->_sFilter1Value = bx_process_input($sFilter1);
+            $this->_aQueryAppend['filter1'] = $this->_sFilter1Value;
+        }
+    }
+
+    protected function _switcherChecked2State($isChecked)
+    {
+        return $isChecked ? 'active' : 'hidden';
+    }
+
+    protected function _switcherState2Checked($mixedState)
+    {
+        return 'active' == $mixedState ? true : false;
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(strpos($sFilter, $this->_sParamsDivider) !== false)
+            list($this->_sFilter1Value, $sFilter) = explode($this->_sParamsDivider, $sFilter);
+
+    	if(!empty($this->_sFilter1Value))
+        	$this->_aOptions['source'] .= $this->_oModule->_oDb->prepare(" AND `status`=?", $this->_sFilter1Value);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    //--- Layout methods ---//
+	protected function _getFilterControls()
+    {
+        parent::_getFilterControls();
+
+        return  $this->_getFilterSelectOne($this->_sFilter1Name, $this->_sFilter1Value, $this->_aFilter1Values) . $this->_getSearchInput();
+    }
+
+	protected function _getCellTitle($mixedValue, $sKey, $aField, $aRow)
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        bx_import('BxDolPermalinks');
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aRow[$CNF['FIELD_ID']]);
+
+        $mixedValue = $this->_oTemplate->parseHtmlByName('bx_a.html', array(
+            'href' => $sUrl,
+            'title' => $mixedValue,
+            'bx_repeat:attrs' => array(),
+            'content' => $mixedValue
+        ));
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAdded($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(bx_time_js($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAuthor($mixedValue, $sKey, $aField, $aRow)
+    {
+    	$oProfile = $this->_getProfileObject($aRow['author']);
+    	$sProfile = $oProfile->getDisplayName();
+
+        $mixedValue = $this->_oTemplate->parseHtmlByName('bx_a.html', array(
+            'href' => $oProfile->getUrl(),
+            'title' => $sProfile,
+            'bx_repeat:attrs' => array(),
+            'content' => $sProfile
+        ));
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionEdit($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+		bx_import('BxDolPermalinks');
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_EDIT_ENTRY'] . '&id=' . $aRow[$CNF['FIELD_ID']]);
+
+    	$a['attr'] = array_merge($a['attr'], array(
+    		"onclick" => "window.open('" . $sUrl . "','_self');"
+    	));
+
+    	return $this->_getActionDefault ($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getProfileObject($iId)
+    {
+    	bx_import('BxDolProfile');
+        $oProfile = BxDolProfile::getInstance($iId);
+        if (!$oProfile) {
+            bx_import('BxDolProfileUndefined');
+            $oProfile = BxDolProfileUndefined::getInstance();
+        }
+        return $oProfile;
+    }
+}
+
+/** @} */

+ 31 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextGridCommon.php

@@ -0,0 +1,31 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ * 
+ * @{
+ */
+
+bx_import('BxBaseModTextGridAdministration');
+
+class BxBaseModTextGridCommon extends BxBaseModTextGridAdministration
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sManageType = 'common';
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+		$this->_aOptions['source'] .= $this->_oModule->_oDb->prepare(" AND `author`=?", bx_get_logged_profile_id());
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+}
+
+/** @} */

+ 29 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextGridModeration.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ * 
+ * @{
+ */
+
+bx_import('BxBaseModTextGridAdministration');
+
+class BxBaseModTextGridModeration extends BxBaseModTextGridAdministration
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sManageType = 'moderation';
+    }
+
+	public function performActionDelete($aParams = array())
+    {
+        $this->_echoResultJson(array());
+    }
+}
+
+/** @} */

+ 25 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextMenuManageTools.php

@@ -0,0 +1,25 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxBaseModGeneralMenuManageTools');
+
+/**
+ * Manage tools submenu
+ */
+class BxBaseModTextMenuManageTools extends BxBaseModGeneralMenuManageTools
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+    }
+}
+
+/** @} */

+ 365 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextModule.php

@@ -0,0 +1,365 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import ('BxBaseModGeneralModule');
+bx_import ('BxDolAcl');
+
+/**
+ * Base module class for text based modules
+ */
+class BxBaseModTextModule extends BxBaseModGeneralModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    // ====== SERVICE METHODS
+	public function serviceGetMenuAddonManageTools()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'status' => 'hidden'
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+	public function serviceGetMenuAddonManageToolsProfileStats()
+	{
+		bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+        $o = new $sClass();
+        $o->fillFilters(array(
+			'author' => bx_get_logged_profile_id()
+        ));
+        $o->unsetPaginate();
+
+        return $o->getNum();
+	}
+
+    /**
+     * Display pablic entries
+     * @return HTML string
+     */
+    public function serviceBrowsePublic ()
+    {
+        return $this->_serviceBrowse ('public', false, BX_DB_PADDING_DEF, true);
+    }
+
+    /**
+     * Display featured entries
+     * @return HTML string
+     */
+    public function serviceBrowsePopular ()
+    {
+        return $this->_serviceBrowse ('popular', false, BX_DB_PADDING_DEF, true);
+    }
+
+    /**
+     * Display entries of the author
+     * @return HTML string
+     */
+    public function serviceBrowseAuthor ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+        return $this->_serviceBrowse ('author', array('author' => $iProfileId), BX_DB_PADDING_DEF, true);
+    }
+
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($iContentId = 0)
+    {
+        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;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        bx_import('BxDolPermalinks');
+        $sUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+        $aCustomParams = false;
+        if ($aContentInfo[$CNF['FIELD_THUMB']]) {
+            bx_import('BxDolStorage');
+            $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+            if ($oStorage && ($sImgUrl = $oStorage->getFileUrlById($aContentInfo[$CNF['FIELD_THUMB']]))) {
+                $aCustomParams = array (
+                    'img_url' => $sImgUrl,
+                    'img_url_encoded' => rawurlencode($sImgUrl),
+                );
+            }
+        }
+
+        //TODO: Rebuild using menus engine when it will be ready for such elements like Vote, Share, etc.
+        $sVotes = '';
+        bx_import('BxDolVote');
+        $oVotes = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $aContentInfo[$CNF['FIELD_ID']]);
+        if ($oVotes)
+            $sVotes = $oVotes->getElementBlock(array('show_do_vote_as_button' => true));
+
+        $sShare = '';
+        if (BxDolRequest::serviceExists('bx_timeline', 'get_share_element_block'))
+            $sShare = BxDolService::call('bx_timeline', 'get_share_element_block', array(bx_get_logged_profile_id(), $this->_aModule['name'], 'added', $aContentInfo[$CNF['FIELD_ID']], array('show_do_share_as_button' => true)));
+
+        bx_import('BxTemplSocialSharing');
+        $sSocial = BxTemplSocialSharing::getInstance()->getCode($iContentId, $this->_aModule['name'], BX_DOL_URL_ROOT . $sUrl, $aContentInfo[$CNF['FIELD_TITLE']], $aCustomParams);
+
+        return $this->_oTemplate->parseHtmlByName('entry-share.html', array(
+            'vote' => $sVotes,
+            'share' => $sShare,
+            'social' => $sSocial,
+        ));
+        //TODO: Rebuild using menus engine when it will be ready for such elements like Vote, Share, etc.
+    }
+
+    /**
+     * Entry text with some additional controls
+     */
+    public function serviceEntityTextBlock ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('viewDataEntry', $iContentId);
+    }
+
+    /**
+     * Entry location info
+     */
+    public function serviceEntityLocation ($iContentId = 0)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        return $this->_oTemplate->entryLocation ($iContentId);
+    }
+
+    /**
+     * Entry comments
+     */
+    public function serviceEntityComments ($iContentId = 0)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        bx_import('BxDolCmts');
+        $oCmts = BxDolCmts::getObjectInstance($this->_oConfig->CNF['OBJECT_COMMENTS'], $iContentId);
+        if (!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getCommentsBlock(0, 0, false);
+    }
+
+    /**
+     * Entry author block
+     */
+    public function serviceEntityAuthor ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryAuthor', $iContentId);
+    }
+
+    /**
+     * Entry attachments block
+     */
+    public function serviceEntityAttachments ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryAttachments', $iContentId);
+    }
+
+    /**
+     * My entries actions block
+     */
+    public function serviceMyEntriesActions ($iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId || $iProfileId != $this->_iProfileId)
+            return false;
+
+        bx_import('BxTemplMenu');
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+	/**
+     * Data for Notification
+     */
+    public function serviceGetNotificationsData()
+    {
+        return array(
+            'handlers' => array(
+                array('type' => 'insert', 'alert_unit' => $this->_aModule['name'], 'alert_action' => 'added', 'module_name' => $this->_aModule['name'], 'module_method' => 'get_notifications_post', 'module_class' => 'Module'),
+                array('type' => 'update', 'alert_unit' => $this->_aModule['name'], 'alert_action' => 'edited'),
+                array('type' => 'delete', 'alert_unit' => $this->_aModule['name'], 'alert_action' => 'deleted')
+            ),
+            'alerts' => array(
+                array('unit' => $this->_aModule['name'], 'action' => 'added'),
+                array('unit' => $this->_aModule['name'], 'action' => 'edited'),
+                array('unit' => $this->_aModule['name'], 'action' => 'deleted'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Timeline
+     */
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+		$CNF = &$this->_oConfig->CNF;
+
+        bx_import('BxDolPermalinks');
+        $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' => _t($CNF['T']['txt_sample_single']),
+			'entry_url' => $sEntryUrl,
+			'entry_caption' => $sEntryCaption,
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+    /**
+     * Data for Timeline
+     */
+    public function serviceGetTimelineData()
+    {
+        return array(
+            'handlers' => array(
+                array('type' => 'insert', 'alert_unit' => $this->_aModule['name'], 'alert_action' => 'added', 'module_name' => $this->_aModule['name'], 'module_method' => 'get_timeline_post', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('type' => 'update', 'alert_unit' => $this->_aModule['name'], 'alert_action' => 'edited'),
+                array('type' => 'delete', 'alert_unit' => $this->_aModule['name'], 'alert_action' => 'deleted')
+            ),
+            'alerts' => array(
+                array('unit' => $this->_aModule['name'], 'action' => 'added'),
+                array('unit' => $this->_aModule['name'], 'action' => 'edited'),
+                array('unit' => $this->_aModule['name'], 'action' => 'deleted'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Timeline
+     */
+    public function serviceGetTimelinePost($aEvent)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return '';
+
+        $CNF = &$this->_oConfig->CNF;
+
+        bx_import('BxDolPermalinks');
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+        //--- Image(s)
+        $sImage = '';
+        if (isset($aContentInfo[$CNF['FIELD_THUMB']]) && $aContentInfo[$CNF['FIELD_THUMB']]) {
+            bx_import('BxDolStorage');
+            $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+            if($oStorage)
+                $sImage = $oStorage->getFileUrlById($aContentInfo[$CNF['FIELD_THUMB']]);
+        }
+
+        $aImages = array();
+        if(!empty($sImage))
+        	$aImages = array(
+				array('url' => $sUrl, 'src' => $sImage)
+			);
+
+        //--- Votes
+        bx_import('BxDolVote');
+        $oVotes = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $aEvent['object_id']);
+
+        $aVotes = array();
+        if ($oVotes && $oVotes->isEnabled())
+            $aVotes = array(
+                'system' => $CNF['OBJECT_VOTES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['votes']
+            );
+
+        //--- Comments
+        bx_import('BxDolCmts');
+        $oCmts = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $aEvent['object_id']);
+
+        $aComments = array();
+        if($oCmts && $oCmts->isEnabled())
+            $aComments = array(
+                'system' => $CNF['OBJECT_COMMENTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['comments']
+            );
+
+        return array(
+            'owner_id' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+            'content' => array(
+                'sample' => _t($CNF['T']['txt_sample_single']),
+                'url' => $sUrl,
+                'title' => isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...'),
+                'text' => $aContentInfo[$CNF['FIELD_TEXT']],
+                'images' => $aImages,
+            ), //a string to display or array to parse default template before displaying.
+            'votes' => $aVotes,
+            'comments' => $aComments,
+            'title' => '', //may be empty.
+            'description' => '' //may be empty.
+        );
+    }
+
+    // ====== PERMISSION METHODS
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedSetThumb ()
+    {
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'set thumb', $this->getName(), false);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    // ====== PROTECTED METHODS
+
+    protected function _buildRssParams($sMode, $aArgs)
+    {
+        $aParams = array ();
+        $sMode = bx_process_input($sMode);
+        switch ($sMode) {
+            case 'author':
+                $aParams = array('author' => isset($aArgs[0]) ? (int)$aArgs[0] : '');
+                break;
+        }
+
+        return $aParams;
+    }
+}
+
+/** @} */

+ 207 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/classes/BxBaseModTextTemplate.php

@@ -0,0 +1,207 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    BaseText Base classes for text modules
+ * @ingroup     DolphinModules
+ *
+ * @{
+ */
+
+bx_import('BxBaseModGeneralTemplate');
+
+/**
+ * Module representation.
+ */
+class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
+{
+    function __construct(&$oConfig, &$oDb)
+    {
+        parent::__construct($oConfig, $oDb);
+    }
+
+    function unit ($aData, $isCheckPrivateContent = true, $sTemplateName = 'unit.html')
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        if ($isCheckPrivateContent && CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $oModule->checkAllowedView($aData))) {
+            $aVars = array (
+                'summary' => $sMsg,
+            );
+            return $this->parseHtmlByName('unit_private.html', $aVars);
+        }
+
+        // get thumb url
+        $sPhotoThumb = '';
+        if ($aData[$CNF['FIELD_THUMB']]) {
+            bx_import('BxDolImageTranscoder');
+            $oImagesTranscoder = BxDolImageTranscoder::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+            if ($oImagesTranscoder)
+                $sPhotoThumb = $oImagesTranscoder->getImageUrl($aData[$CNF['FIELD_THUMB']]);
+        }
+
+        // get entry url
+        bx_import('BxDolPermalinks');
+        $sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aData[$CNF['FIELD_ID']]);
+
+        bx_import('BxDolProfile');
+        $oProfile = BxDolProfile::getInstance($aData[$CNF['FIELD_AUTHOR']]);
+        if (!$oProfile) {
+            bx_import('BxDolProfileUndefined');
+            $oProfile = BxDolProfileUndefined::getInstance();
+        }
+
+        // get summary
+        $sLinkMore = ' <a title="' . bx_html_attribute(_t('_sys_read_more', $aData[$CNF['FIELD_TITLE']])) . '" href="' . $sUrl . '"><i class="sys-icon ellipsis-h"></i></a>';
+        $sSummary = strmaxtextlen($aData[$CNF['FIELD_TEXT']], (int)getParam($CNF['PARAM_CHARS_SUMMARY']), $sLinkMore);
+        $sSummaryPlain = BxTemplFunctions::getInstance()->getStringWithLimitedLength(strip_tags($sSummary), (int)getParam($CNF['PARAM_CHARS_SUMMARY_PLAIN']));
+
+        // generate html
+        $aVars = array (
+            'id' => $aData[$CNF['FIELD_ID']],
+            'content_url' => $sUrl,
+            'title' => bx_process_output($aData[$CNF['FIELD_TITLE']]),
+            'summary' => $sSummary,
+            'author' => $oProfile->getDisplayName(),
+            'author_url' => $oProfile->getUrl(),
+            '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:thumb' => array (
+                'condition' => $sPhotoThumb,
+                'content' => array (
+                    'title' => bx_process_output($aData[$CNF['FIELD_TITLE']]),
+                    'summary_attr' => bx_html_attribute($sSummaryPlain),
+                    'content_url' => $sUrl,
+                    'thumb_url' => $sPhotoThumb ? $sPhotoThumb : '',
+                ),
+            ),
+            'bx_if:no_thumb' => array (
+                'condition' => !$sPhotoThumb,
+                'content' => array (
+                    'content_url' => $sUrl,
+                    'summary_plain' => $sSummaryPlain,
+                ),
+            ),
+        );
+
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    function entryText ($aData, $sTemplateName = 'entry-text.html')
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        $aVars = $aData;
+        $aVars['entry_title'] = isset($aData[$CNF['FIELD_TITLE']]) ? $aData[$CNF['FIELD_TITLE']] : '';
+        $aVars['entry_text'] = $aData[$CNF['FIELD_TEXT']];
+
+        if (!empty($CNF['OBJECT_METATAGS'])) {
+            bx_import('BxDolMetatags');
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $aVars['text'] = $oMetatags->keywordsParse($aData[$CNF['FIELD_ID']], $aVars['text']);
+        }
+
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    function entryLocation ($iContentId)
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        if (empty($CNF['OBJECT_METATAGS']))
+            return '';
+
+        bx_import('BxDolMetatags');
+        $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        $aVars = array (
+            'location' => $oMetatags->locationsString($iContentId)
+        );
+        return $this->parseHtmlByName('entry-location.html', $aVars);
+    }
+
+    function entryAuthor ($aData, $sTemplateName = 'author.html')
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        $oProfile = BxDolProfile::getInstance($aData[$CNF['FIELD_AUTHOR']]);
+        if (!$oProfile) {
+            bx_import('BxDolProfileUndefined');
+            $oProfile = BxDolProfileUndefined::getInstance();
+        }
+        if (!$oProfile)
+            return '';
+
+        $aVars = array (
+            'author_url' => $oProfile->getUrl(),
+            'author_thumb_url' => $oProfile->getThumb(),
+            'author_title' => $oProfile->getDisplayName(),
+            'author_desc' => $this->getAuthorDesc($aData),
+        );
+        return $this->parseHtmlByName($sTemplateName, $aVars);
+    }
+
+    function getAuthorDesc ($aData)
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        return bx_time_js($aData[$oModule->_oConfig->CNF['FIELD_ADDED']], BX_FORMAT_DATE);
+    }
+
+    function entryAttachments ($aData)
+    {
+        $oModule = BxDolModule::getInstance($this->MODULE);
+        $CNF = &$oModule->_oConfig->CNF;
+
+        bx_import('BxTemplFunctions');
+        bx_import('BxDolStorage');
+        bx_import('BxDolImageTranscoder');
+
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
+        $oTranscoder = BxDolImageTranscoder::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW']);
+
+        $aGhostFiles = $oStorage->getGhosts ($aData[$CNF['FIELD_AUTHOR']], $aData[$CNF['FIELD_ID']]);
+        if (!$aGhostFiles)
+            return false;
+
+        foreach ($aGhostFiles as $k => $a) {
+
+            $isImage = $oTranscoder && (0 == strncmp('image/', $a['mime_type'], 6)); // preview for images only and transcoder object for preview must be defined
+            $sUrlOriginal = $oStorage->getFileUrlById($a['id']);
+            $sImgPopupId = 'bx-messages-atachment-popup-' . $a['id'];
+
+            // images are displayed with preview and popup upon clicking
+            $aGhostFiles[$k]['bx_if:image'] = array (
+                'condition' => $isImage,
+                'content' => array (
+                    'url_original' => $sUrlOriginal,
+                    'attr_file_name' => bx_html_attribute($a['file_name']),
+                    'popup_id' => $sImgPopupId,
+                    'url_preview' => $oTranscoder->getImageUrl($a['id']),
+                    'popup' =>  BxTemplFunctions::getInstance()->transBox($sImgPopupId, '<img src="' . $sUrlOriginal . '" />', true, true),
+                ),
+            );
+
+            // non-images are displayed as text links to original file
+            $aGhostFiles[$k]['bx_if:not_image'] = array (
+                'condition' => !$isImage,
+                'content' => array (
+                    'url_original' => $sUrlOriginal,
+                    'attr_file_name' => bx_html_attribute($a['file_name']),
+                    'file_name' => bx_process_output($a['file_name']),
+                ),
+            );
+        }
+
+        $aVars = array(
+            'bx_repeat:attachments' => $aGhostFiles,
+        );
+        return $this->parseHtmlByName('attachments.html', $aVars);
+    }
+}
+
+/** @} */

+ 3 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/template/entry-location.html

@@ -0,0 +1,3 @@
+
+<p class="bx-base-text-entity-location bx-def-font-grayed">__location__</p>
+

+ 6 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/template/entry-text.html

@@ -0,0 +1,6 @@
+
+<div class="bx-base-text-entity-content bx-def-constraint-list bx-def-constraint-pre bx-def-constraint-object bx-def-constraint-iframe bx-def-constraint-img">
+    <h1>__title__</h1>
+    __text__
+</div>
+

+ 16 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/modules/base/text/template/unit.html

@@ -0,0 +1,16 @@
+
+<div class="bx-base-text-unit bx-base-text-unit-ext bx-def-margin-bottom">
+    <h1 class="bx-def-padding-sec-top"><a href="__content_url__">__title__</a></h1>
+    <div class="bx-base-text-unit-info bx-def-margin-sec-top">
+        <span class="bx-def-font-grayed">__entry_posting_date__</span>
+        <span class="bx-base-text-unit-info-div bx-def-font-grayed">&#183;</span>
+        <a class="bx-base-text-unit-author" href="__author_url__">__author__</a>
+    </div>
+    <div class="bx-base-text-unit-summary bx-def-margin-sec-top bx-def-margin-sec-bottom bx-clearfix">
+        <bx_if:thumb>
+            <img class="bx-base-text-unit-thumb bx-def-gal-img-size bx-def-round-corners bx-def-margin-right" alt="__title__" src="__thumb_url__" />
+        </bx_if:thumb>
+        __summary__
+    </div> 
+</div>
+

+ 799 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/classes/BxDolStudioInstaller.php

@@ -0,0 +1,799 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+
+bx_import('BxDolInstallerUtils');
+
+define("BX_DOL_STUDIO_INSTALLER_SUCCESS", 0);
+define("BX_DOL_STUDIO_INSTALLER_FAILED", 1);
+
+/**
+ * Base class for Installer classes in modules engine.
+ *
+ * The class contains different check functions which are used during the installation process.
+ * An object of the class is created automatically with Dolphin's modules installer.
+ * Installation/Uninstallation process can be controlled with config.php file located in  [module]/install/ folder.
+ *
+ *
+ * Example of usage:
+ * @see any module included in the default Dolphin's package.
+ *
+ *
+ * Memberships/ACL:
+ * Doesn't depend on user's membership.
+ *
+ *
+ * Alerts:
+ * no alerts available
+ *
+ */
+
+class BxDolStudioInstaller extends BxDolInstallerUtils
+{
+    protected $oDb;
+
+    protected $_aConfig;
+    protected $_sBasePath;
+    protected $_sHomePath;
+    protected $_sModulePath;
+
+    protected $_bUseFtp;
+    protected $_aActions;
+
+    protected $_bShowOnSuccess = false;
+
+    function __construct($aConfig)
+    {
+        parent::__construct();
+
+        $this->oDb = bx_instance('BxDolStudioInstallerQuery');
+
+        $this->_aConfig = $aConfig;
+        $this->_sBasePath = BX_DIRECTORY_PATH_MODULES;
+        $this->_sHomePath = $this->_sBasePath . $aConfig['home_dir'];
+        $this->_sModulePath = $this->_sBasePath . $aConfig['home_dir'];
+
+        $this->_bUseFtp = BX_FORCE_USE_FTP_FILE_TRANSFER;
+
+        $this->_aActions = array(
+            'perform_install' => array(
+                'title' => '',
+                'success' => _t('_adm_msg_modules_success_install'),
+                'failed' => ''
+            ),
+            'perform_uninstall' => array(
+                'title' => '',
+                'success' => _t('_adm_msg_modules_success_uninstall'),
+                'failed' => ''
+            ),
+            'perform_enable' => array(
+                'title' => '',
+                'success' => _t('_adm_msg_modules_success_enable'),
+                'failed' => ''
+            ),
+            'perform_disable' => array(
+                'title' => '',
+                'success' => _t('_adm_msg_modules_success_disable'),
+                'failed' => ''
+            ),
+            'check_script_version' => array(
+                'title' => _t('_adm_txt_modules_check_script_version'),
+            ),
+            'check_dependencies' => array(
+                'title' => _t('_adm_txt_modules_check_dependencies'),
+            ),
+            'show_introduction' => array(
+                'title' => _t('_adm_txt_modules_show_introduction'),
+            ),
+            'move_sources' => array(
+                'title' => _t('_adm_txt_modules_move_sources'),
+            ),
+            'execute_sql' => array(
+                'title' => _t('_adm_txt_modules_execute_sql'),
+            ),
+            'install_language' => array(
+                'title' => _t('_adm_txt_modules_install_language'),
+            ),
+            'update_languages' => array(
+                'title' => _t('_adm_txt_modules_update_languages'),
+            ),
+            'update_relations' => array(
+                'title' => _t('_adm_txt_modules_update_relations'),
+            ),
+            'process_deleted_profiles' => array(
+                'title' => _t('_adm_txt_modules_process_deleted_profiles'),
+            ),
+            'recompile_global_paramaters' => array(
+                'title' => _t('_adm_txt_modules_recompile_global_paramaters'),
+            ),
+            'recompile_menus' => array(
+                'title' => _t('_adm_txt_modules_recompile_menus'),
+            ),
+            'recompile_site_stats' => array(
+                'title' => _t('_adm_txt_modules_recompile_site_stats'),
+            ),
+            'recompile_comments' => array(
+                'title' => _t('_adm_txt_modules_recompile_comments'),
+            ),
+            'recompile_member_actions' => array(
+                'title' => _t('_adm_txt_modules_recompile_member_actions'),
+            ),
+            'recompile_votes' => array(
+                'title' => _t('_adm_txt_modules_recompile_votes'),
+            ),
+            'recompile_search' => array(
+                'title' => _t('_adm_txt_modules_recompile_search'),
+            ),
+            'recompile_injections' => array(
+                'title' => _t('_adm_txt_modules_recompile_injections'),
+            ),
+            'recompile_permalinks' => array(
+                'title' => _t('_adm_txt_modules_recompile_permalinks'),
+            ),
+            'recompile_alerts' => array(
+                'title' => _t('_adm_txt_modules_recompile_alerts'),
+            ),
+            'clear_db_cache' => array(
+                'title' => _t('_adm_txt_modules_clear_db_cache'),
+            ),
+            'show_conclusion' => array(
+                'title' => _t('_adm_txt_modules_show_conclusion'),
+            ),
+        );
+
+        $this->_aNonHashableFiles = array(
+            'install',
+            'updates'
+        );
+    }
+
+    public function install($aParams, $bAutoEnable = false)
+    {
+    	$bAutoEnable = $bAutoEnable || (isset($aParams['auto_enable']) && (bool)$aParams['auto_enable']);
+    	$bHtmlResponce = isset($aParams['html_response']) && (bool)$aParams['html_response'];
+
+        //--- Check whether the module was already installed ---//
+        if($this->oDb->isModule($this->_aConfig['home_uri']))
+            return array(
+                'message' => _t('_adm_err_modules_already_installed'),
+                'result' => false
+            );
+
+        $aResult = array();
+        bx_alert('system', 'before_install', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        if ($aResult && !$aResult['result'])
+            return $aResult;
+
+        //--- Check mandatory settings ---//
+        if($this->oDb->isModuleParamsUsed($this->_aConfig['home_uri'], $this->_aConfig['home_dir'], $this->_aConfig['db_prefix'], $this->_aConfig['class_prefix']))
+            return array(
+                'message' => _t('_adm_txt_modules_params_used'),
+                'result' => false
+            );
+
+        //--- Check version compatibility ---//
+        if(!$this->_isCompatibleWith())
+            return array(
+                'message' => $this->_displayResult('check_script_version', false, '_adm_err_modules_wrong_version_script', $bHtmlResponce),
+                'result' => false
+            );
+
+        //--- Check for available translations ---//
+        $oFile = $this->_getFileManager();
+        $sModuleUri = $this->_aConfig['home_uri'];
+
+        $aLanguages = $this->oDb->getModulesBy(array('type' => 'languages'));
+        foreach($aLanguages as $aLanguage) {
+            if($aLanguage['uri'] == 'en')
+                continue;
+
+			$aLanguageConfig = self::getModuleConfig(BX_DIRECTORY_PATH_MODULES . $aLanguage['path'] . '/install/config.php');
+			if(empty($aLanguageConfig))
+				continue;
+
+            if(!isset($aLanguageConfig['includes'][$sModuleUri]) || empty($aLanguageConfig['includes'][$sModuleUri]))
+                continue;
+
+            $sSrcPath = 'modules/' . $aLanguage['path'] . 'install/data/' . $aLanguageConfig['includes'][$sModuleUri];
+            $sDstPath = $aLanguageConfig['includes'][$sModuleUri];
+            $oFile->copy($sSrcPath, $sDstPath);
+        }
+
+        //--- Check actions ---//
+        $aResult = $this->_perform('install', $aParams);
+        if($aResult['result']) {
+            $sDependencies = "";
+            if(isset($this->_aConfig['install']['check_dependencies']) && (int)$this->_aConfig['install']['check_dependencies'] == 1 && isset($this->_aConfig['dependencies']) && is_array($this->_aConfig['dependencies']))
+                $this->_aConfig['dependencies'] = array_keys($this->_aConfig['dependencies']);
+
+            $iModuleId = $this->oDb->insertModule($this->_aConfig);
+
+            bx_import('BxDolModule');
+            $sTitleKey = BxDolModule::getTitleKey($this->_aConfig['home_uri']);
+
+            bx_import('BxDolStudioLanguagesUtils');
+            $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+            $oLanguages->addLanguageString($sTitleKey, $this->_aConfig['title']);
+            $oLanguages->compileLanguage();
+
+            bx_import('BxDolStudioInstallerUtils');
+            BxDolStudioInstallerUtils::getInstance()->checkModules();
+
+            $aFiles = array();
+            $this->hashFiles($this->_sModulePath, $aFiles);
+
+            foreach($aFiles as $aFile)
+                $this->oDb->insertModuleTrack($iModuleId, $aFile);
+
+            $this->oDb->cleanMemory('sys_modules_' . $this->_aConfig['home_uri']);
+            $this->oDb->cleanMemory('sys_modules_' . $iModuleId);
+            $this->oDb->cleanMemory('sys_modules');
+        }
+
+        bx_alert('system', 'install', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+
+	    if($aResult['result'] && $bAutoEnable) {
+			$aResultEnable = $this->enable($aParams);
+
+			$aResult['result'] = $aResult['result'] & $aResultEnable['result'];
+			$aResult['message'] = $aResult['message'] . $aResultEnable['message'];
+		}
+
+        return $aResult;
+    }
+
+    public function uninstall($aParams, $bAutoDisable = false)
+    {
+    	$bAutoDisable = $bAutoDisable || (isset($aParams['auto_disable']) && (bool)$aParams['auto_disable']);
+    	$bHtmlResponce = isset($aParams['html_response']) && (bool)$aParams['html_response'];
+
+        //--- Check whether the module was already uninstalled ---//
+        if(!$this->oDb->isModule($this->_aConfig['home_uri']))
+            return array(
+                'message' => _t('_adm_err_modules_already_uninstalled'),
+                'result' => false
+            );
+
+		if($bAutoDisable) {
+            $aResultDisable = $this->disable($aParams);
+            if(!$aResultDisable['result'])
+            	return $aResultDisable;
+        }
+
+        $aResult = array();
+        bx_alert('system', 'before_uninstall', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        if ($aResult && !$aResult['result'])
+            return $aResult;
+
+        //--- Check for dependent modules ---//
+        $bDependent = false;
+        $aDependents = $this->oDb->getDependent($this->_aConfig['home_uri']);
+        if(is_array($aDependents) && !empty($aDependents)) {
+            $bDependent = true;
+
+            $sMessage = '<br />' . _t('_adm_err_modules_wrong_dependency_uninstall') . '<br />';
+            foreach($aDependents as $aDependent)
+                $sMessage .= $aDependent['title'] . '<br />';
+        }
+
+        if($bDependent)
+            return array(
+                'message' => $this->_displayResult('check_dependencies', false, $sMessage, $bHtmlResponce),
+                'result' => false
+            );
+
+        $aResult = $this->_perform('uninstall', $aParams);
+        if($aResult['result']) {
+            $iModuleId = $this->oDb->deleteModule($this->_aConfig);
+
+            bx_import('BxDolModule');
+            $sTitleKey = BxDolModule::getTitleKey($this->_aConfig['home_uri']);
+
+            bx_import('BxDolStudioLanguagesUtils');
+            $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+            $oLanguages->deleteLanguageString($sTitleKey);
+            $oLanguages->compileLanguage();
+
+            $this->oDb->cleanMemory ('sys_modules_' . $this->_aConfig['home_uri']);
+            $this->oDb->cleanMemory ('sys_modules_' . $iModuleId);
+            $this->oDb->cleanMemory ('sys_modules');
+        }
+
+        if($bAutoDisable) {
+	        $aResult['result'] = $aResultDisable['result'] & $aResult['result'];
+			$aResult['message'] = $aResultDisable['message'] . $aResult['message'];
+        }
+
+        bx_alert('system', 'uninstall', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        return $aResult;
+    }
+
+    public function delete($aParams)
+    {
+    	$aError = array(
+			'message' => _t('_adm_err_modules_cannot_remove_package'),
+			'result' => false
+		);
+
+    	$oFile = $this->_getFileManager();
+    	if(empty($oFile))
+    		return $aError;
+
+        if(!$oFile->delete('modules/' . $this->_aConfig['home_dir']))
+            return $aError;
+
+        return array(
+            'message' => '', //_t('_adm_msg_modules_success_delete'),
+            'result' => true
+        );
+    }
+
+    public function recompile($aParams)
+    {
+        bx_import('BxDolStudioLanguagesUtils');
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+
+        $aResult = array('message' => '', 'result' => false);
+
+        $aLanguages = $this->oDb->getLanguages();
+        if (isAdmin() && !empty($aLanguages)) {
+            $this->_updateLanguage(false, current($aLanguages));
+
+            $bResult = false;
+            foreach($aLanguages as $aLanguage) {
+                $bResult = $this->_updateLanguage(true, $aLanguage) && $oLanguages->compileLanguage($aLanguage['id']);
+                $aResult['message'] .= $aLanguage['title'] . ': <span style="color:' . ($bResult ? 'green' : 'red') . '">' . _t($bResult ? '_adm_txt_modules_process_action_success' : '_adm_txt_modules_process_action_failed') . '</span><br />';
+
+                $aResult['result'] |= $bResult;
+            }
+        }
+
+        return $aResult;
+    }
+
+    public function enable($aParams)
+    {
+        $aModule = $this->oDb->getModuleByUri($this->_aConfig['home_uri']);
+
+        //--- Check whether the module is installed ---//
+        if(empty($aModule) || !is_array($aModule))
+            return array(
+                'message' => _t('_adm_err_modules_module_not_installed'),
+                'result' => false
+            );
+
+        //--- Check whether the module is already enabled ---//
+        if((int)$aModule['enabled'] != 0)
+            return array(
+                'message' => _t('_adm_err_modules_already_enabled'),
+                'result' => false
+            );
+
+		$aResult = array();
+        bx_alert('system', 'before_enable', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        if ($aResult && !$aResult['result'])
+            return $aResult;
+
+        $aResult = $this->_perform('enable', $aParams);
+        if($aResult['result']) {
+            $this->oDb->enableModuleByUri($aModule['uri']);
+
+            $this->oDb->cleanMemory('sys_modules_' . $aModule['uri']);
+            $this->oDb->cleanMemory('sys_modules_' . $aModule['id']);
+            $this->oDb->cleanMemory('sys_modules');
+        }
+
+        bx_alert('system', 'enable', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        return $aResult;
+    }
+
+    public function disable($aParams)
+    {
+    	$bHtmlResponce = isset($aParams['html_response']) && (bool)$aParams['html_response'];
+
+        $aModule = $this->oDb->getModuleByUri($this->_aConfig['home_uri']);
+
+        //--- Check whether the module is installed ---//
+        if(empty($aModule) || !is_array($aModule))
+            return array(
+                'message' => _t('_adm_err_modules_module_not_installed'),
+                'result' => false
+            );
+
+        //--- Check whether the module is already disabled ---//
+        if((int)$aModule['enabled'] == 0)
+            return array(
+                'message' => _t('_adm_err_modules_already_disabled'),
+                'result' => false
+            );
+
+		$aResult = array();
+        bx_alert('system', 'before_disable', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        if ($aResult && !$aResult['result'])
+            return $aResult;
+
+        //--- Check for dependent modules ---//
+        $bDependent = false;
+        $aDependents = $this->oDb->getDependent($this->_aConfig['home_uri']);
+        if(is_array($aDependents) && !empty($aDependents)) {
+            $bDependent = true;
+
+            $sMessage = '<br />' . _t('_adm_err_modules_wrong_dependency_disable') . '<br />';
+            foreach($aDependents as $aDependent)
+                $sMessage .= $aDependent['title'] . '<br />';
+        }
+
+        if($bDependent)
+            return array(
+                'message' => $this->_displayResult('check_dependencies', false, $sMessage, $bHtmlResponce),
+                'result' => false
+            );
+
+        $aResult = $this->_perform('disable', $aParams);
+        if($aResult['result']) {
+            $this->oDb->disableModuleByUri($aModule['uri']);
+
+            $this->oDb->cleanMemory('sys_modules_' . $aModule['uri']);
+            $this->oDb->cleanMemory('sys_modules_' . $aModule['id']);
+            $this->oDb->cleanMemory('sys_modules');
+        }
+
+        bx_alert('system', 'disable', 0, false, array ('config' => $this->_aConfig, 'result' => &$aResult));
+        return $aResult;
+    }
+
+    protected function filePathWithoutBase($sPath)
+    {
+        return bx_ltrim_str($sPath, $this->_sModulePath);
+    }
+
+    function _perform($sOperationName, $aParams = array())
+    {
+    	$bHtmlResponce = isset($aParams['html_response']) && (bool)$aParams['html_response'];
+
+        if(!defined('BX_SKIP_INSTALL_CHECK') && !defined('BX_DOL_CRON_EXECUTE') && !$GLOBALS['logged']['admin'])
+            return array('message' => '_adm_mod_err_only_admin_can_perform_operations_with_modules', 'result' => false);
+
+        $sMessage = '';
+        foreach($this->_aConfig[$sOperationName] as $sAction => $iEnabled) {
+            $sMethod = 'action' . bx_gen_method_name($sAction);
+            if($iEnabled == 0 || !method_exists($this, $sMethod))
+                continue;
+
+            $mixedResult = $this->$sMethod($sOperationName);
+
+            //--- On Success ---//
+            if((is_int($mixedResult) && (int)$mixedResult == BX_DOL_STUDIO_INSTALLER_SUCCESS) || (isset($mixedResult['code']) && (int)$mixedResult['code'] == BX_DOL_STUDIO_INSTALLER_SUCCESS)) {
+                $sMessage .= $this->_displayResult($sAction, true, isset($mixedResult['content']) ? $mixedResult['content'] : '', $bHtmlResponce);
+                continue;
+            }
+
+            //--- On Failed ---//
+            $sMethodFailed = $sMethod . 'Failed';
+            return array('message' => $this->_displayResult($sAction, false, method_exists($this, $sMethodFailed) ? $this->$sMethodFailed($mixedResult) : $this->actionOperationFailed($mixedResult), $bHtmlResponce), 'result' => false);
+        }
+
+        if($this->_bShowOnSuccess)
+            $sMessage = $this->_aActions['perform_' . $sOperationName]['success'] . $sMessage;
+
+        return array('message' => $sMessage, 'result' => true);
+    }
+
+    function _displayResult($sAction, $bResult, $sResult = '', $bHtmlResponse = true)
+    {
+    	if($bResult && !in_array($sAction, array('show_introduction', 'show_conclusion')) && !$this->_bShowOnSuccess)
+            return '';
+
+        $sTitle = $this->_aActions[$sAction]['title'] . ' ';
+        if(!empty($sResult))
+            $sResult = (substr($sResult, 0, 1) == '_' ? _t($sResult) : $sResult) . '<br />';
+
+		$sContent = !empty($sResult) ? $sResult : _t($bResult ? '_adm_txt_modules_process_action_success' : '_adm_err_modules_process_action_failed') . '<br />';
+		if(!$bHtmlResponse)
+			return $sTitle . $sContent;
+
+		bx_import('BxDolStudioTemplate');
+        return BxDolStudioTemplate::getInstance()->parseHtmlByName('mod_action_result_step.html', array(
+        	'color' => $bResult ? 'green' : 'red',
+            'title' => $sTitle,
+            'content' => $sContent
+        ));
+    }
+
+    //--- Action Methods ---//
+    function actionOperationFailed($mixedResult)
+    {
+        return _t('_adm_err_modules_process_action_failed');
+    }
+    function actionCheckDependencies($sOperation)
+    {
+        $sContent = '';
+
+        if(in_array($sOperation, array('install', 'enable', 'update'))) {
+            if(!isset($this->_aConfig['dependencies']) || !is_array($this->_aConfig['dependencies']))
+                return BX_DOL_STUDIO_INSTALLER_SUCCESS;
+
+            foreach($this->_aConfig['dependencies'] as $sModuleUri => $sModuleTitle)
+                if(!$this->oDb->isModule($sModuleUri))
+                    $sContent .= $sModuleTitle . '<br />';
+
+            if(!empty($sContent))
+                $sContent = '<br />' . _t('_adm_err_modules_wrong_dependency_install') . '<br />' . $sContent;
+        }
+
+        return empty($sContent) ? BX_DOL_STUDIO_INSTALLER_SUCCESS : array('code' => BX_DOL_STUDIO_INSTALLER_FAILED, 'content' => $sContent);
+    }
+    function actionCheckDependenciesFailed($mixedResult)
+    {
+        return $mixedResult['content'];
+    }
+    function actionShowIntroduction($sOperation)
+    {
+        if(!isset($this->_aConfig[$sOperation . '_info']['introduction']))
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+        $sPath = $this->_sHomePath . 'install/info/' . $this->_aConfig[$sOperation . '_info']['introduction'];
+        return file_exists($sPath) ? array("code" => BX_DOL_STUDIO_INSTALLER_SUCCESS, "content" => file_get_contents($sPath)) : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionShowConclusion($sOperation)
+    {
+        if(!isset($this->_aConfig[$sOperation . '_info']['conclusion']))
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+        $sPath = $this->_sHomePath . 'install/info/' . $this->_aConfig[$sOperation . '_info']['conclusion'];
+        return file_exists($sPath) ? array("code" => BX_DOL_STUDIO_INSTALLER_SUCCESS, "content" => file_get_contents($sPath)) : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionMoveSources($sOperation)
+    {
+        $oFile = $this->_getFileManager();
+        $aInstalled = array_merge(array('system'), $this->oDb->getModulesUri());
+
+        $bResult = true;
+        foreach($this->_aConfig['includes'] as $sUri => $sPath) {
+            if(!in_array($sUri, $aInstalled) || empty($sPath))
+                continue;
+
+            if($sOperation == 'install') {
+                $sSrcPath = 'modules/' . $this->_aConfig['home_dir'] . 'install/data/' . $sPath;
+                $sDstPath = $sPath;
+                $bResult &= $oFile->copy($sSrcPath, $sDstPath);
+            } else if($sOperation == 'uninstall')
+                $bResult &= $oFile->delete($sPath);
+        }
+
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionExecuteSql($sOperation)
+    {
+        switch($sOperation) {
+            case 'install':
+                $this->actionExecuteSql('disable');
+                $this->actionExecuteSql('uninstall');
+                break;
+            case 'enable':
+                $this->actionExecuteSql('disable');
+                break;
+        }
+
+        $mixedResult = $this->oDb->executeSQL($this->_sHomePath . 'install/sql/' . $sOperation . '.sql', $this->getMarkersForDb());
+
+        return $mixedResult === true ? BX_DOL_STUDIO_INSTALLER_SUCCESS : array('code' => BX_DOL_STUDIO_INSTALLER_FAILED, 'content' => $mixedResult);
+    }
+    function actionExecuteSqlFailed($mixedResult)
+    {
+    	if(is_int($mixedResult))
+    		return $this->actionOperationFailed($mixedResult);
+
+        $sResult = '<br />' . _t('_adm_err_modules_wrong_mysql_query') . '<br />';
+        foreach($mixedResult['content'] as $aQuery) {
+            $sResult .= _t('_adm_err_modules_wrong_mysql_query_msg', $aQuery['error']) . '<br />';
+            $sResult .= '<pre>' . $aQuery['query'] . '</pre>';
+        }
+        return $sResult;
+    }
+    function actionInstallLanguage($sOperation)
+    {
+        bx_import('BxDolStudioLanguagesUtils');
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+
+        $sLanguage = isset($this->_aConfig['home_uri']) ? $this->_aConfig['home_uri'] : '';
+
+        $bResult = true;
+        if($sOperation == 'install')
+            $bResult = $oLanguages->installLanguage(array('path' => $this->_aConfig['home_dir'], 'uri' => $this->_aConfig['home_uri'], 'lang_category' => $this->_aConfig['language_category']),false);
+
+        return $bResult && $oLanguages->compileLanguage(0, true) ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionUpdateLanguages($sOperation)
+    {
+        if(!isset($this->_aConfig['language_category']) || empty($this->_aConfig['language_category']))
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+        bx_import('BxDolStudioLanguagesUtils');
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+
+        $bResult = true;
+        $aResult = array();
+
+        //--- Process Language Category ---//
+        if($sOperation == 'install')
+            $iCategoryId = $oLanguages->addLanguageCategory($this->_aConfig['language_category']);
+
+        //--- Process languages' key=>value pears ---//
+        $aModule = array(
+            'path' => $this->_aConfig['home_dir'],
+            'uri' => $this->_aConfig['home_uri'],
+            'lang_category' => $this->_aConfig['language_category']
+        );
+
+        if($sOperation == 'install')
+            $bResult = $oLanguages->restoreLanguage(0, $aModule, false);
+        else if($sOperation == 'uninstall')
+            $bResult = $oLanguages->removeLanguageByModule($aModule, false);
+
+        if($sOperation == 'uninstall' && $bResult)
+            $oLanguages->deleteLanguageCategory($this->_aConfig['language_category']);
+
+        return $bResult && $oLanguages->compileLanguage(0, true) ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+	function actionUpdateRelations($sOperation)
+    {
+        if(!in_array($sOperation, array('install', 'uninstall', 'enable', 'disable'))) 
+        	return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+		if(empty($this->_aConfig['relations']) || !is_array($this->_aConfig['relations']))
+            return BX_DOL_STUDIO_INSTALLER_SUCCESS;
+
+		foreach($this->_aConfig['relations'] as $sModule) {
+			if(!$this->oDb->isModuleByName($sModule))
+				continue;
+
+			$aRelation = $this->oDb->getRelationsBy(array('type' => 'module', 'value' => $sModule));
+			if(empty($aRelation) || empty($aRelation['on_' . $sOperation]) || !BxDolRequest::serviceExists($aRelation['module'], $aRelation['on_' . $sOperation]))
+				continue;
+
+			BxDolService::call($aRelation['module'], $aRelation['on_' . $sOperation], array($this->_aConfig['home_uri']));
+		}
+
+        return BX_DOL_STUDIO_INSTALLER_SUCCESS;
+    }
+	function actionProcessDeletedProfiles($sOperation)
+    {
+        if(!in_array($sOperation, array('install', 'uninstall', 'enable', 'disable'))) 
+        	return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+        bx_import('BxDolProfileQuery');
+        $o = BxDolProfileQuery::getInstance();
+        $o->processDeletedProfiles();
+
+        return BX_DOL_STUDIO_INSTALLER_SUCCESS;
+    }
+    function actionRecompileGlobalParamaters($sOperation)
+    {
+        $bResult = $this->oDb->cacheParamsClear();
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileMenus($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_objects_menu');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileSiteStats($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_stat_site');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileComments($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_objects_cmts');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileMemberActions($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_objects_actions');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileVotes($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_objects_vote');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileInjections($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_injections.inc');
+        $bResult = $bResult && $this->oDb->cleanCache('sys_injections_admin.inc');
+
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompilePermalinks($sOperation)
+    {
+        $bResult = true;
+
+        bx_import('BxDolPermalinks');
+        $bResult = $bResult && BxDolPermalinks::getInstance()->cacheInvalidate();
+
+        $bResult = $bResult && ($this->actionRecompileMenus($sOperation) == BX_DOL_STUDIO_INSTALLER_SUCCESS);
+
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionRecompileAlerts($sOperation)
+    {
+        $bResult = $this->oDb->cleanCache('sys_alerts');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+    function actionClearDbCache($sOperation)
+    {
+        $oCache = $this->oDb->getDbCacheObject();
+
+        $bResult = $oCache->removeAllByPrefix('db_');
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+
+    //--- Get/Set Methods ---//
+    function getVendor()
+    {
+        return $this->_aConfig['vendor'];
+    }
+    function getName()
+    {
+        return $this->_aConfig['name'];
+    }
+    function getTitle()
+    {
+        return $this->_aConfig['title'];
+    }
+    function getHomeDir()
+    {
+        return $this->_aConfig['home_dir'];
+    }
+
+    //--- Protected methods ---//
+    protected function _getFileManager()
+    {
+        $oFile = null;
+
+        if($this->_bUseFtp) {
+            bx_import('BxDolFtp');
+            $oFile = new BxDolFtp(isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost', getParam('sys_ftp_login'), getParam('sys_ftp_password'), getParam('sys_ftp_dir'));
+
+            if(!$oFile->connect())
+                return null;
+
+            if(!$oFile->isDolphin())
+                return null;
+        }
+        else {
+            bx_import('BxDolFile');
+            $oFile = BxDolFile::getInstance();
+        }
+
+        return $oFile;
+    }
+
+    protected function _isCompatibleWith()
+    {
+    	$sVersionCur = bx_get_ver();
+
+    	$bCompatible = false;
+        if(isset($this->_aConfig['compatible_with']) && is_array($this->_aConfig['compatible_with']))
+            foreach($this->_aConfig['compatible_with'] as $iKey => $sVersionReq)
+            	$bCompatible = $bCompatible || (version_compare($sVersionCur, $sVersionReq, '>=') == 1);
+
+		return $bCompatible;
+    }
+
+    protected function getMarkersForDb()
+    {
+        return array(
+            'from' => array('{db_name}'),
+            'to' => array(defined('BX_DATABASE_NAME') ? BX_DATABASE_NAME : ''),
+        );
+    }
+}
+
+/** @} */

+ 229 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/classes/BxDolStudioUpdater.php

@@ -0,0 +1,229 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+
+bx_import('BxDolStudioInstaller');
+
+class BxDolStudioUpdater extends BxDolStudioInstaller
+{
+    public function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+        $this->_sModulePath = $this->_sBasePath . $aConfig['module_dir'];
+
+        $this->_aActions = array_merge($this->_aActions, array(
+            'check_module_exists' => array(
+                'title' => _t('_adm_txt_modules_check_module_exists'),
+            ),
+            'check_module_matches' => array(
+                'title' => _t('_adm_txt_modules_check_module_matches'),
+            ),
+            'check_module_version' => array(
+                'title' => _t('_adm_txt_modules_check_module_version'),
+            ),
+            'check_module_hash' => array(
+                'title' => _t('_adm_txt_modules_check_module_hash'),
+            ),
+            'update_files' => array(
+                'title' => _t('_adm_txt_modules_update_files'),
+            ),
+            'restore_languages' => array(
+                'title' => _t('_adm_txt_modules_restore_languages'),
+            ),
+        ));
+    }
+
+    public function update($aParams)
+    {
+    	$bHtmlResponce = isset($aParams['html_response']) && (bool)$aParams['html_response'];
+
+        $aResult = array(
+            'operation_title' => _t('_adm_txt_modules_operation_update', $this->_aConfig['title'], $this->_aConfig['version_from'], $this->_aConfig['version_to'])
+        );
+
+        //--- Check for module to update ---//
+        $aModuleInfo = $this->oDb->getModulesBy(array('type' => 'path_and_uri', 'path' => $this->_aConfig['module_dir'], 'uri' => $this->_aConfig['module_uri']));
+        if(!$aModuleInfo)
+            return array_merge($aResult, array(
+                'message' => $this->_displayResult('check_module_exists', false, '_adm_err_modules_module_not_found', $bHtmlResponce),
+                'result' => false
+            ));
+
+		if(isset($aParams['module_name']) && strcmp($aParams['module_name'], $aModuleInfo['name']) != 0)
+			return array_merge($aResult, array(
+                'message' => $this->_displayResult('check_module_matches', false, '_adm_err_modules_module_not_match', $bHtmlResponce),
+                'result' => false
+            ));
+
+		//--- Check Dolphin version compatibility ---//
+        if(!$this->_isCompatibleWith())
+            return array(
+                'message' => $this->_displayResult('check_script_version', false, _t('_adm_err_modules_wrong_version_script_update', $aModuleInfo['title']), $bHtmlResponce),
+                'result' => false
+            );
+
+        //--- Check version ---//
+        if(version_compare($aModuleInfo['version'], $this->_aConfig['version_from']) != 0)
+            return array_merge($aResult, array(
+                'message' => $this->_displayResult('check_module_version', false, '_adm_err_modules_wrong_version', $bHtmlResponce),
+                'result' => false
+            ));
+
+        //--- Check hash ---//
+        $aFiles = array();
+        $this->hashFiles(BX_DIRECTORY_PATH_ROOT . 'modules/' . $this->_aConfig['module_dir'], $aFiles);
+
+        list($aFilesChanged, $fChangedPercent) = $this->hashCheck($aFiles, $aModuleInfo['id']);
+        $bAutoupdateForceModifiedFiles = getParam('sys_autoupdate_force_modified_files') == 'on';
+
+    	if(!empty($aFilesChanged) && !$bAutoupdateForceModifiedFiles) 
+    		return array_merge($aResult, array(
+                'message' => $this->_displayResult('check_module_hash', false, '_adm_err_modules_module_was_modified', $bHtmlResponce),
+                'result' => false
+            ));
+		else if($fChangedPercent > BX_FORCE_AUTOUPDATE_MAX_CHANGED_FILES_PERCENT && $bAutoupdateForceModifiedFiles) 
+			return array_merge($aResult, array(
+                'message' => $this->_displayResult('check_module_hash', false, _t('_sys_upgrade_files_checksum_failed_too_many', round($fChangedPercent * 100)), $bHtmlResponce),
+                'result' => false
+            ));
+
+        //--- Perform action and check results ---//
+        $aResult = array_merge($aResult, $this->_perform('install', $aParams));
+        if($aResult['result']) {
+            $this->oDb->updateModule(array('version' => $this->_aConfig['version_to']), array('id' => $aModuleInfo['id']));
+
+            $this->oDb->deleteModuleTrackFiles($aModuleInfo['id']);
+
+            $aFiles = array();
+            $this->hashFiles(BX_DIRECTORY_PATH_ROOT . 'modules/' . $this->_aConfig['module_dir'], $aFiles);
+            foreach($aFiles as $aFile)
+                $this->oDb->insertModuleTrack($aModuleInfo['id'], $aFile);
+
+            //--- Remove update pckage ---//
+            $this->delete($aParams);
+        }
+
+        return $aResult;
+    }
+
+    //--- Action Methods ---//
+    public function actionUpdateFiles($bInstall = true)
+    {
+        $sPath = $this->_sHomePath . 'source/';
+        if(!file_exists($sPath))
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+		$oFile = $this->_getFileManager();
+		if(empty($oFile))
+			return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+		if(!$oFile->copy($sPath . '*', 'modules/' . $this->_aConfig['module_dir']))
+			return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+		if(!empty($this->_aConfig['delete_files']) && is_array($this->_aConfig['delete_files']))
+			foreach($this->_aConfig['delete_files'] as $sFile)
+				if(!$oFile->delete('modules/' . $this->_aConfig['module_dir'] . $sFile))
+					return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+        return BX_DOL_STUDIO_INSTALLER_SUCCESS;
+    }
+
+    public function actionExecuteSql($sOperation)
+    {
+    	$aModule = $this->oDb->getModuleByUri($this->_aConfig['module_uri']);
+    	if(empty($aModule))
+    		return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+    	$sPathInstall = $this->_sHomePath . 'install/sql/install.sql';
+    	$sPathEnable = $this->_sHomePath . 'install/sql/enable.sql';
+
+    	$mixedResult = true;
+    	if(file_exists($sPathInstall))
+        	$mixedResult = $this->oDb->executeSQL($sPathInstall, $this->getMarkersForDb());
+
+		if($mixedResult === true && (int)$aModule['enabled'] == 1 && file_exists($sPathEnable))
+			$mixedResult = $this->oDb->executeSQL($sPathEnable, $this->getMarkersForDb());
+
+        return $mixedResult === true ? BX_DOL_STUDIO_INSTALLER_SUCCESS : array('code' => BX_DOL_STUDIO_INSTALLER_FAILED, 'content' => $mixedResult);
+    }
+
+    public function actionUpdateLanguages($bInstall = true)
+    {
+        $aConfig = self::getModuleConfig($this->_sHomePath . 'install/config.php');
+        if(empty($aConfig) || !is_array($aConfig))
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+    	bx_import('BxDolStudioLanguagesUtils');
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+        $aLanguages = $oLanguages->getLanguages();
+
+        $iCategoryId = !empty($aConfig['language_category']) ? $oLanguages->getLanguageCategory($aConfig['language_category']) : 0;
+
+        foreach($aLanguages as $sName => $sTitle)
+            $this->_updateLanguage($bInstall, $sName, $iCategoryId);
+
+        return $oLanguages->compileLanguage(0, true) ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+
+    /*
+     * Restore module's language files.
+     * 
+     * Note. Mainly the action is needed for Updates in 'language' type modules. 
+     * It should be used after 'update_files' action if some changes were done in module's language files. 
+     */
+    public function actionRestoreLanguages($bInstall = true)
+    {
+    	$aConfig = self::getModuleConfig($this->_sHomePath . 'install/config.php');
+        if(empty($aConfig) || !is_array($aConfig) || empty($aConfig['module_uri']))
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
+
+		bx_import('BxDolStudioLanguagesUtils');
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+        $aLanguages = $oLanguages->getLanguages();
+
+        $bResult = true;
+        foreach($aLanguages as $sName => $sTitle)
+        	$bResult &= $oLanguages->restoreLanguage($sName, $aConfig['module_uri']);
+
+        return $bResult ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
+    }
+
+    protected function _updateLanguage($bInstall, $sLanguage, $iCategory = 0)
+    {
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+
+        $sPath = $this->_sHomePath . 'install/langs/' . $sLanguage . '.xml';
+        $aLanguageInfo = $oLanguages->readLanguage($sPath, 'update');
+        if(empty($aLanguageInfo))
+            return false;
+
+        $iLanguage = $oLanguages->getLangId($sLanguage);
+
+        if(!empty($aLanguageInfo['category']))
+        	$iCategory = $oLanguages->getLanguageCategory($aLanguageInfo['category']);
+
+        //--- Process delete. Note. Deletion is performed for all languages. ---//
+        if(isset($aLanguageInfo['strings_del']))
+        	foreach($aLanguageInfo['strings_del'] as $sKey => $sValue)
+        		$oLanguages->deleteLanguageString($sKey, 0, false);
+
+        //--- Process add. Note. Key's category will be updated if it doesn't match. ---//
+        if(isset($aLanguageInfo['strings_add']))
+        	foreach($aLanguageInfo['strings_add'] as $sKey => $sValue)
+        		$oLanguages->addLanguageString($sKey, $sValue, $iLanguage, $iCategory, false);
+
+        //--- Process update. Note. Key's category will be updated if it doesn't match. ---//
+        if(isset($aLanguageInfo['strings_upd']))
+        	foreach($aLanguageInfo['strings_upd'] as $sKey => $sValue)
+        		$oLanguages->updateLanguageString($sKey, $sValue, $iLanguage, $iCategory, false);
+
+        return true;
+    }
+}
+
+/** @} */

+ 60 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/template/_header.html

@@ -0,0 +1,60 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=__page_charset__" />
+        <title>__page_header__</title>
+
+        __meta_info__
+	    __page_robots__
+
+        <bx_include_css />
+
+        __dol_images__
+        __dol_lang__
+        __dol_options__
+
+        <bx_include_js />
+        <script defer type="text/javascript">
+            var sUrlRoot = '<bx_url_root />';
+            var sUrlStudio = '<bx_url_studio />';
+            var aSpinnerOpts = {
+                lines: 7, // The number of lines to draw
+                length: 0, // The length of each line
+                width: 8, // The line thickness
+                radius: 7, // The radius of the inner circle
+                corners: 1, // Corner roundness (0..1)
+                rotate: 0, // The rotation offset
+                direction: 1, // 1: clockwise, -1: counterclockwise
+                color: '#165ea1', // #rgb or #rrggbb or array of colors
+                speed: 1, // Rounds per second
+                trail: 60, // Afterglow percentage
+                shadow: false, // Whether to render a shadow
+                hwaccel: false, // Whether to use hardware acceleration
+                className: 'bx-sys-spinner', // The CSS class to assign to the spinner
+                zIndex: 2e9, // The z-index (defaults to 2000000000)
+                top: '10px', // Top position relative to parent in px
+                left: '10px' // Left position relative to parent in px
+            };
+            var aSpinnerSmallOpts = $.extend({}, aSpinnerOpts, {
+                lines: 6,
+                width: 6,
+                radius: 4,
+                color: '#333',
+                top: 'auto',
+                left: 'auto'
+            }); 
+
+			$(document).ready(function () {
+				bx_time('__lang__');
+
+                bx_activate_anim_icons('#3e8685');
+			});
+        </script>
+        __extra_js__
+
+        <bx_injection:injection_head />
+
+    </head>
+
+    <body class="bx-def-font bx-def-color-bg-page bx-std-bg<bx_injection:injection_body_style />" <bx_injection:injection_body /> >
+        <bx_injection:injection_header />

+ 251 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/template/css/launcher.css

@@ -0,0 +1,251 @@
+body.bx-std-bg {
+	background-color: #3b8686;
+}
+
+/**
+ * Launcher
+ */
+div.bx-std-launcher {
+	position: relative;
+	float: left;
+	clear: both;
+	width: 100%;
+	left: 0px;
+}
+
+
+/**
+ * Widget
+ */
+div.bx-std-widgets-wrp {
+    position: relative;
+	float: left;
+    top: 0px;
+	width: 100%;
+}
+div.bx-std-widgets-center {
+    position: relative;
+    float: left;
+    left: 50%;
+}
+div.bx-std-widgets {
+    position: relative;
+    float: left;
+    left: -50%;
+	min-height: 238px;
+    margin: 0px 30px;
+}
+div.bx-std-widgets-highlight {
+    background-color: red;
+}
+div.bx-std-widget {
+    position: relative;
+    float: left;
+	padding: 30px;
+}
+div.bx-std-widget-empty {
+    width: 128px;
+    height: 178px;
+}
+div.bx-std-widget-nl {
+	clear: both;
+}
+
+.bx-std-widget-dragging {
+    -webkit-transform: scale(1.05, 1.05);
+    -moz-transform: scale(1.05, 1.05);
+    -ms-transform: scale(1.05, 1.05);
+    transform: scale(1.05, 1.05);
+}
+
+.bx-std-widget-icon {
+    position: relative;
+    top:0px;
+    width: 128px;
+    height: 128px;
+
+    background-color:transparent;
+
+    line-height: 128px;
+}
+
+.bx-std-widget-icon i.sys-icon {
+	position: absolute;
+	z-index: 2;
+	left: 0px;
+	width: 128px;
+
+    font-size: 96px;
+    text-align: center;
+    text-shadow: none;
+    color: white;
+}
+
+.bx-std-widget-shadow {
+    position:absolute;
+    top:2px;
+    left:2px;
+    width: 124px;
+    height: 124px;
+    z-index:1;
+
+    -moz-border-radius: 32px;
+    -webkit-border-radius: 32px;
+    border-radius: 32px;
+
+    /* big shadow */
+    -moz-box-shadow: 0px 15px 20px rgba(51, 51, 51, 0.55);
+    -webkit-box-shadow: 0px 15px 20px rgba(51, 51, 51, 0.55);
+    box-shadow: 0px 15px 20px rgba(51, 51, 51, 0.55);
+}
+
+.bx-std-widget-icon-trans {
+    -webkit-transition: all 0.15s ease-out;
+    -moz-transition: all 0.15s ease-out;
+    -ms-transition: all 0.15s ease-out;
+    -o-transition: all 0.15s ease-out;
+}
+
+.bx-std-widget-icon:hover {
+    top:3px;
+}
+
+.bx-std-widget-icon:hover .bx-std-widget-shadow {
+    -moz-box-shadow: 0px 12px 15px rgba(51, 51, 51, 0.55);
+    -webkit-box-shadow: 0px 12px 15px rgba(51, 51, 51, 0.55);
+    box-shadow: 0px 12px 15px rgba(51, 51, 51, 0.55);
+}
+
+.bx-std-widget-dragging .bx-std-widget-shadow {
+    -moz-box-shadow: 0px 25px 40px rgba(51, 51, 51, 0.55);
+    -webkit-box-shadow: 0px 25px 40px rgba(51, 51, 51, 0.55);
+    box-shadow: 0px 25px 40px rgba(51, 51, 51, 0.55);
+}
+
+.bx-std-widget-icon-disabled {
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
+    filter: alpha(opacity=40);
+    -moz-opacity: 0.4;
+    -khtml-opacity: 0.4;
+    opacity: 0.4;    
+}
+
+.bx-std-widget-link > img {
+    position:relative;
+    z-index:2;
+
+    width: 128px;
+    height: 128px;
+
+    background-color: transparent;
+    background-position:0px 0px;
+    background-repeat:no-repeat;
+}
+
+div.bx-std-widget-icon-jitter {
+    position: absolute;
+    top: 30px;
+    left: 30px;
+    width: 128px;
+    height: 128px;
+    z-index: 5;
+
+    background-color: transparent;
+}
+
+div.bx-std-widget-icon-jitter img {
+    position:absolute;
+
+    bottom:-5px;
+    left:24px;
+    width:80px;
+    height:40px;
+
+    background-color: transparent;
+    background-image:url(../images/bx-std-widget-drag.png);
+
+    cursor: move;
+}
+
+div.bx-std-widget-notices {
+    position: absolute;
+    top: 0px;
+    left: 45px;
+    width: 38px;
+    height: 27px;
+    z-index: 3;
+    overflow:visible;
+    
+    color:#fff;
+    font-weight:bold;
+    text-shadow: #633 0px -1px 0px;        
+    line-height:25px;
+    text-align:center;
+
+    background-color: transparent;
+    background-image: url(../images/bx-std-bg-widget-notice.png);
+}
+div.bx-std-widget-actions {
+    position: absolute;
+    display: none;
+    top: 10px;
+    right: 10px;
+    height: 40px;
+    z-index: 10;
+}
+div.bx-std-widget-action {
+    position: relative;
+    float: left;
+    width: 40px;
+    height: 40px;
+	line-height: 40px;
+}
+div.bx-std-widget-action i.sys-icon {
+	position: absolute;
+	left: 0px;
+	width: 40px;
+
+    font-size: 32px;
+    text-align: center;
+    text-shadow: none;
+    color: white;
+}
+.bx-std-widget-loading {
+    display:none;
+    position:absolute;
+    top: 176px;
+    left: 30px;
+    width: 128px;
+    height: 15px;
+
+    background-image: url(../images/loading.gif);
+    background-repeat: no-repeat;
+    background-position: center center;
+
+    -moz-opacity: 0.5;
+    -khtml-opacity: 0.5;
+    opacity: 0.5;
+}
+div.bx-std-widget-caption {
+    position: relative;
+    width: 128px;
+    height: 50px;
+    line-height: 50px;
+    text-align: center; 
+    overflow: hidden;
+    text-overflow: ellipsis;
+
+    text-shadow: #000 0px 5px 12px;
+}
+div.bx-std-widget-caption-jitter {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 128px;
+    height: 50px;
+}
+div.bx-std-widget-caption,
+div.bx-std-widget-caption a {
+    color: #ffffff;
+    font-weight:normal;
+}

+ 51 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/studio/template/css/menu_top.css

@@ -0,0 +1,51 @@
+div.bx-std-menu-top-wrp {
+    position: relative;
+    width: 100%;
+	min-height: 50px;
+}
+div.bx-std-menu-top {
+	height: 50px;
+}
+div.bx-std-menu-top-left {
+    position: absolute;
+	z-index: 3;
+    top: 0px;
+    left: 10px;
+	height: 50px;
+}
+div.bx-std-page-caption div.bx-std-menu-top-left {
+    position: absolute;
+    top: 10px;
+    left: 10px;
+	height: 50px;
+}
+div.bx-std-menu-top-center {
+    position: relative;
+	margin: 0px auto;
+	line-height: 50px;
+	text-align: center;
+	font-size: 2rem;
+}
+div.bx-std-menu-top-right {
+    position: absolute;
+    top: 0px;
+    right: 10px;
+	height: 50px;
+}
+div.bx-std-page-caption div.bx-std-menu-top-right {
+    position: absolute;
+    z-index: 3;
+    top: 10px;
+    right: 10px;
+	height: 50px;
+}
+
+div.bx-std-menu-top i.sys-icon {
+    -webkit-transition: all 0.2s ease-in-out;
+    transition: all 0.2s ease-in-out;
+}
+
+div.bx-std-menu-top li.bx-menu-tab-active i.sys-icon {
+	color: #ffe900;
+    text-shadow:0 0 4px #ffe900;
+}

+ 77 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/_header.html

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="__lang__">
+<head>
+    <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" />
+    <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 />
+
+    __dol_images__
+    __dol_lang__
+    __dol_options__
+
+    <bx_include_js />
+    <script type="text/javascript" language="javascript">
+
+        var sUrlRoot = '<bx_url_root />';
+
+        var aSpinnerOpts = {
+            lines: 7, // The number of lines to draw
+            length: 0, // The length of each line
+            width: 8, // The line thickness
+            radius: 7, // The radius of the inner circle
+            corners: 1, // Corner roundness (0..1)
+            rotate: 0, // The rotation offset
+            direction: 1, // 1: clockwise, -1: counterclockwise
+            color: '#3a8686', // #rgb or #rrggbb or array of colors
+            speed: 1, // Rounds per second
+            trail: 60, // Afterglow percentage
+            shadow: false, // Whether to render a shadow
+            hwaccel: false, // Whether to use hardware acceleration
+            className: 'bx-sys-spinner', // The CSS class to assign to the spinner
+            zIndex: 2e9, // The z-index (defaults to 2000000000)
+            top: '10px', // Top position relative to parent in px
+            left: '10px' // Left position relative to parent in px
+        };
+
+        var aSpinnerSmallOpts = $.extend({}, aSpinnerOpts, {
+            lines: 6,
+            width: 6,
+            radius: 4,
+            color: '#333',
+            top: 'auto',
+            left: 'auto'
+        });
+
+        $(document).ready(function () {
+            bx_time('__lang__');
+
+            $('#bx-toolbar').css({opacity: 0.9});
+
+            bx_activate_anim_icons('#3e8685');
+        });
+
+        (function(w) {
+            var dpr = (w.devicePixelRatio === undefined ? 1 : w.devicePixelRatio);
+            if ($.cookie('devicePixelRatio') == dpr || dpr < 2)
+                return;
+            $.cookie('devicePixelRatio', dpr, {expires: 365, path: '/'});
+            window.location.reload();
+        })(window);
+
+    </script>
+    __extra_js__
+
+    <bx_injection:injection_head />
+
+</head>
+
+<body class="bx-def-font bx-def-color-bg-page" <bx_injection:injection_body /> >
+    <bx_injection:injection_header />    

+ 67 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/_sub_header.html

@@ -0,0 +1,67 @@
+<bx_include_auto:_header.html />
+
+<div class="bx-main">
+
+	<bx_injection:top />
+
+	<div id="bx-toolbar" class="bx-def-z-index-nav">
+	
+	    <div class="bx-def-centered bx-def-padding-sec-left bx-def-padding-sec-right bx-def-page-width">
+	
+	        <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_logo_top_menu />
+	
+	<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-left bx-def-padding-sec-right bx-def-page-width">
+
+	            <bx_injection:injection_top_menu_before />
+
+        	    <bx_menu:sys_site_submenu />
+
+	            <bx_injection:injection_top_menu_after />
+
+	        </div>
+	
+	    </div>
+	
+	</div>
+	
+	<bx_injection:injection_between_content_menu />
+
+	<div id="bx-content-wrapper">
+
+		__informer__
+		
+		<div id="bx-content-wrapper" class="bx-page-wrapper bx-def-centered bx-def-padding-sec-left bx-def-padding-sec-right bx-def-page-width">
+		
+		    <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">

+ 1 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/bx_div.html

@@ -0,0 +1 @@
+<div <bx_repeat:attrs>__key__="__value__"</bx_repeat:attrs>>__content__</div>

+ 15 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/comment_actions.html

@@ -0,0 +1,15 @@
+<div class="__style_prefix__-actions bx-def-margin-sec-top bx-clearfix">
+	<div class="__style_prefix__-ago bx-def-margin-right">
+		<bx_if:show_ago_link>
+			<a class="__style_prefix__-ago" href="__view_link__" rel="nofollow">__ago__</a>
+		</bx_if:show_ago_link>
+		<bx_if:show_ago_text>__ago__</bx_if:show_ago_text>
+	</div>
+	<bx_if:show_menu_manage>
+		<div class="__style_prefix__-comment-manage-link bx-def-margin-right">
+			<a class="__style_prefix__-comment-manage bx-def-margin-sec-left-auto" href="javascript:void(0)" onclick="__js_object__.toggleManagePopup(this, __id__); return false;" title="<bx_text_attribute:_Manage />"><i class="sys-icon ellipsis-h"></i></a>
+		</div>
+		__popup_text__
+	</bx_if:show_menu_manage>
+	__menu_actions__
+</div>

+ 7 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/comments_notification.html

@@ -0,0 +1,7 @@
+<div id="__html_id__" class="__style_prefix__-notification bx-def-border bx-def-round-corners" style="display:none;">
+	<div class="__style_prefix__-cnt bx-def-padding-sec-topbottom">
+		<div class="__style_prefix__-message">
+			<a href="__url__">__message__</a>
+		</div>
+	</div>
+</div>

+ 82 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/_mixins.less

@@ -0,0 +1,82 @@
+@import url(_remixins.less);
+
+.radius(@val) {
+    @rem-val: (unit(@val) / @base-font-size-px);
+	border-radius:~"@{rem-val}rem";
+	-moz-border-radius:~"@{rem-val}rem";
+	-webkit-border-radius:~"@{rem-val}rem";
+
+	-moz-background-clip: padding;
+	-webkit-background-clip: padding-box;
+	background-clip: padding-box;
+}
+
+.border (@width: 1px, @type: solid, @color: #ccc) {
+    @rem-w: (unit(@width) / @base-font-size-px);
+    border: ~"@{width} @{type} @{color}";
+    border: ~"@{rem-w}rem @{type} @{color}";
+}
+.border-top (@width: 1px, @type: solid, @color: #ccc) {
+    @rem-w: (unit(@width) / @base-font-size-px);
+    border-top: ~"@{width} @{type} @{color}";
+    border-top: ~"@{rem-w}rem @{type} @{color}";
+}
+.border-bottom (@width: 1px, @type: solid, @color: #ccc) {
+    @rem-w: (unit(@width) / @base-font-size-px);
+    border-bottom: ~"@{width} @{type} @{color}";
+    border-bottom: ~"@{rem-w}rem @{type} @{color}";
+}
+.border-left (@width: 1px, @type: solid, @color: #ccc) {
+    @rem-w: (unit(@width) / @base-font-size-px);
+    border-left: ~"@{width} @{type} @{color}";
+    border-left: ~"@{rem-w}rem @{type} @{color}";
+}
+.border-right (@width: 1px, @type: solid, @color: #ccc) {
+    @rem-w: (unit(@width) / @base-font-size-px);
+    border-right: ~"@{width} @{type} @{color}";
+    border-right: ~"@{rem-w}rem @{type} @{color}";
+}
+
+.drop-shadow (@x: 0px, @y: 1px, @blur: 2px, @spread: 0px, @color: rgba(0, 0, 0, .25)) {
+    @rem-x: (unit(@x) / @base-font-size-px);
+    @rem-y: (unit(@y) / @base-font-size-px);
+    @rem-b: (unit(@blur) / @base-font-size-px);
+    @rem-s: (unit(@spread) / @base-font-size-px);
+	-webkit-box-shadow: ~"@{rem-x}rem @{rem-y}rem @{rem-b}rem @{rem-s}rem rgba(0, 0, 0, @alpha)";
+	-moz-box-shadow: ~"@{rem-x}rem @{rem-y}rem @{rem-b}rem @{rem-s}rem rgba(0, 0, 0, @alpha)";
+	box-shadow: ~"@{rem-x}rem @{rem-y}rem @{rem-b}rem @{rem-s}rem rgba(0, 0, 0, @alpha)";
+}
+
+.box-sizing (@type: border-box) {
+	-webkit-box-sizing: @type;
+	-moz-box-sizing: @type;
+    -ms-box-sizing: @type;
+	box-sizing: @type;
+}
+
+.background-color (@color) {
+    @a:percentage((1.0-alpha(@color))/5.0);
+    background-color: hsl(hue(@color), saturation(@color), lightness(@color)+@a);
+    background-color: @color;
+}
+.column-width (@width) {
+    @rem-w: (unit(@width) / @base-font-size-px);
+    -webkit-column-width: ~"@{rem-w}rem";
+    -moz-column-width: ~"@{rem-w}rem";
+    -ms-column-width: ~"@{rem-w}rem";
+    column-width: ~"@{rem-w}rem";
+}
+.column-width-raw (@width) {
+    -webkit-column-width: @width;
+    -moz-column-width: @width;
+    -ms-column-width: @width;
+    column-width: @width;
+}
+
+.blur(@val) {
+    -webkit-filter: blur(@val);
+    -moz-filter: blur(@val);
+    -o-filter: blur(@val);
+    -ms-filter: blur(@val);
+    filter: blur(@val);
+}

+ 219 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/cmts.css

@@ -0,0 +1,219 @@
+    /*--- General Styles ---*/
+    div.cmts {}
+
+    ul.cmts {
+    	position: relative;
+        margin: 0px;
+        padding: 0px;
+        list-style: none;
+    }
+    ul.cmts-margin,
+    div.cmt-reply-margin {
+    	margin-top: 20px;
+    	margin-top: 1.2rem;
+
+		padding-left: 60px;
+    	padding-left: 3.75rem;
+    }
+    li.cmt {
+    	position: relative;
+    }
+    li.cmt-err {
+        color: red;
+    }
+    li.cmt-no    {
+        padding: 10px;
+    }
+    li.cmt-hidden {
+       display: none;
+    }
+    li.cmt-replacement {
+    	position: relative;
+		height: 30px;
+		height: 1.875rem;
+		background-color: #ededed;
+		text-indent: 10px;
+    	text-indent: 0.5rem;
+		line-height: 30px;
+    	line-height: 1.875rem;
+    	overflow: hidden;
+    }
+    div.cmt-cnt {
+		position: relative;
+    }
+
+	/*--- More ---*/
+    li.cmt-more,
+	li.cmt-more-root {
+		text-transform:uppercase;
+	}
+
+	div.cmt-more-cont {
+		font-weight: bold;
+	}
+
+	
+    li.cmt-more-root div.cmt-more-cont {
+    	position: relative;
+    }
+    li.cmt-more-root .bx-btn {
+    	position: relative;
+		float: none;
+    	display: inline-block;
+    	text-transform: uppercase;
+    }    
+
+    /*--- Comment Author ---*/
+	div.cmt-avatar {
+		position: absolute;
+		top: 0px;
+		left: 0px;
+    	text-align: center;
+    }
+    div.cmt-avatar .sys-icon {
+    	font-size: 60px;
+		font-size: 3.75rem;
+    }
+    div.cmt-info {
+    	position: realtive;
+	}
+	div.cmt-head div.cmt-username,
+	div.cmt-head div.cmt-username,
+	div.cmt-head div.cmt-ago,
+	div.cmt-head div.cmt-reply-to,
+	div.cmt-head div.bullet {
+		display: inline-block;
+	}
+	div.cmt-head div.bullet {
+		padding: 0px;
+	}
+
+    div.cmt-reply-to a.cmt-points span {
+		display: inline;
+    	float: none;
+    }
+    
+    span.cmt-posted-ago {
+        text-transform: lovercase;
+    }
+	span.cmt-points {
+        text-transform: lovercase;
+    }
+
+    /*--- Comment Body ---*/
+    div.cmt-cont {
+        position: relative;
+    }
+	div.cmt-cont .bullet {
+		padding: 0px 5px;
+		padding: 0px 0.3rem;
+    }
+    div.cmt-cont .cmt-post-reply .bx-form-element-wrapper {
+		margin-top: 10px;
+    	margin-top: 0.5rem;
+    }
+	div.cmt-cont .cmt-post-reply .bx-form-element-wrapper:first-child {
+		margin-top: 0px;
+    }
+    span.cmt-body-see-more a {
+		font-weight: bold;
+    }
+    div.cmt-attached-preview {
+		overflow:hidden;
+    }
+    div.cmt-attached-preview,
+    div.cmt-attached-preview img {
+    	width: 100px;
+    	width: 6.25rem;
+    	height: 100px;
+    	height: 6.25rem;
+    }
+    div.cmt-attached-preview {
+    	position: relative;
+    	float: left;
+	}
+	div.cmt-attached-preview img {
+		background-position: center center;
+		background-repeat: no-repeat;
+	}
+	
+    /*--- Comment Actions ---*/
+	div.cmt-actions {
+		position: relative;
+	}
+	div.cmt-ago,
+	div.cmt-comment-manage-link, 
+	div.cmt-actions ul.bx-menu-hor,
+	div.cmt-points-wrp {
+		position: relative;
+		float: left;
+	}
+    div.cmt-comment-manage .bx-menu-ver li {
+		white-space: nowrap;
+    }
+
+    /*--- Add Comment/Reply Styles ---*/
+    div.cmt-reply {
+		position: relative;
+    }
+	div.cmt-post-reply {
+        position: relative;
+    }
+
+    /*--- Add Comment/Reply Form Styles ---*/
+    form.cmt-post-reply .bx-form-input-textarea {
+		height: 64px;
+    	height: 4rem;
+    }
+    form.cmt-post-reply .bx-form-input-submit {
+    	text-transform: uppercase;
+    }
+    form.cmt-post-reply div#bx-form-element-cmt_image,
+    form.cmt-post-reply .bx-form-input-files-uploaders {
+		margin-top: 0px;
+    }
+
+    a.cmts-uploader-button {
+		position: absolute;
+    	right: 0px;
+    	text-decoration: none;
+    }
+    div.cmts-uploader-file {
+		position: relative;
+    	float: left;
+    }
+    div.cmts-uploader-file-preview {
+		overflow: hidden;
+    }
+    div.cmts-uploader-file-preview,
+    div.cmts-uploader-file-preview img {
+		width: 100px;
+    	width: 6.25rem;
+    	height: 100px;
+    	height: 6.25rem;
+    }
+    div.cmts-uploader-file-preview img {
+		background-position: center center;
+		background-repeat: no-repeat;
+    }
+
+    /*--- Live Update Notification Styles ---*/
+    div.cmt-notification {
+    	border-color: #616161;
+		border-bottom: none;
+
+    	border-bottom-right-radius: 0px;
+    	border-bottom-left-radius: 0px;
+
+    	background-color: rgba(97, 97, 97, .5);
+    }
+    div.cmt-notification div.cmt-cnt {
+		padding-left: 80px;
+    	padding-left: 5rem;
+    	padding-right: 80px;     	
+    	padding-right: 5rem;
+    }
+    div.cmt-notification a {
+		color: #ffffff;
+    	font-weight: bold;
+    }

+ 464 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/common.css

@@ -0,0 +1,464 @@
+
+* {
+    -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:underline;
+}
+
+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-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;
+    display:inline-block;
+    height:0px;
+    width:0px;
+    overflow:hidden;
+}
+
+.sys-icon-a {
+    width:1.5rem;
+    height:1.5rem;
+}
+
+.bx-search-site {
+    width:20rem;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+/* --------------- design boxes */
+
+.bx-db-container {
+    position:relative;
+}
+
+.bx-db-title {
+    display:inline-block;
+    float:left;
+    line-height:40px;
+}
+
+.bx-db-menu {
+    line-height:40px;
+    float: right;
+    vertical-align:baseline;
+}
+
+.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;
+    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;
+}
+
+.bx-popup-slide-wrapper {
+    opacity:0.9;
+    width:100%;
+}
+
+.bx-popup-slide-wrapper li {
+    text-align:right;
+}
+
+.bx-popup-image-wrapper .bx-popup-close-wrapper,
+.bx-popup-image-wrapper .bx-popup-element-close,
+.bx-popup-with-pointer .bx-popup-close-wrapper,
+.bx-popup-with-pointer .bx-popup-element-close {
+    display:none;
+}
+
+/* --------------- message box */
+
+.bx-msg-box-container {
+    padding:0px 5px;
+    text-align:center;
+}
+
+.bx-msg-box {
+    display:inline-block; 
+    margin-right:auto;
+    margin-left:auto;
+
+    background-color:rgba(200, 200, 200, 0.3);
+
+    text-align:center;
+    font-weight:bold;
+    font-size:1rem;
+    padding:10px;
+    text-transform:uppercase;
+}
+
+/* --------------- loading indicator */
+
+.bx-loading-ajax {
+    position:absolute;
+    width:50px;
+    height:50px;
+    margin-left:auto;
+    margin-right:auto;
+
+    background-color:#b9ddcc;
+    background-color:rgba(121, 189, 154, 0.5);
+
+	border-radius:5px;
+	-moz-border-radius:5px;
+	-webkit-border-radius:5px;
+
+    text-align:center;
+    color:#CCCCCC;
+}
+
+.bx-loading-ajax-btn {
+    display:inline-block;
+    width:20px;
+    height:20px;
+    margin-left:5px;
+    vertical-align:middle;
+}
+.bx-btn .bx-loading-ajax-btn {
+    margin-top:8px;
+    vertical-align:top;
+}
+
+/* --------------- 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;
+
+    line-height:36px;
+    line-height:2.2rem;
+    font-size:1rem;
+	color:#fff;
+	text-decoration:none;
+    cursor: pointer;
+
+    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;
+}
+.bx-btn:active,
+.bx-btn:hover {	
+    background-color: #79bd9a;
+    text-decoration:none;
+}
+.bx-btn:first-child {
+    margin-left:0;
+}
+button.bx-btn,
+input.bx-btn {    
+	padding-top:0px;
+	padding-bottom:0px;
+    font-size:1rem;
+    color:#fff;
+}
+
+.bx-btn img {
+    width:16px;
+    height:16px;
+}
+.bx-btn u {
+    margin-left:2px;
+    text-decoration:none;
+}
+.bx-btn .sys-icon {
+    font-size:1.2em;
+}
+.bx-btn .sys-icon-bigger {
+    font-size:1.4em;
+}
+
+.bx-btn.bx-btn-small {
+    height:24px; 
+    height:1.6rem; 
+    padding-left:0px 8px 0px 8px;
+
+    line-height:24px;
+    line-height:1.5rem;
+    font-size:0.9rem;
+}
+
+.bx-btn.bx-btn-primary {
+    background-color: #357877;
+}
+.bx-btn.bx-btn-primary:hover,
+.bx-btn.bx-btn-primary:active { 
+    background-color: #3a8686;
+}
+
+.bx-btn.bx-btn-disabled:active,
+.bx-btn.bx-btn-disabled:hover,
+.bx-btn.bx-btn-disabled {
+    background-color: #ddd;
+}
+
+.bx-btn.bx-btn-symbol {
+    font-family: "Times New Roman", Georgia, Serif;
+    font-size:1.3rem;
+    line-height:32px;
+}
+.bx-btn.bx-btn-symbol:hover {
+    text-decoration:none;
+}
+.bx-btn.bx-btn-symbol-small {
+    font-size:1rem;
+}
+
+.bx-btn-group .bx-btn {
+    margin-left:0px;
+    margin-right:0px;
+}
+.bx-btn-group .bx-btn:not(:first-child):after {
+    content:"";
+    position:absolute;
+    display:block;
+    top:0px;
+    left:-0.05em;
+    width:0.1em;
+    height:100%;
+    background-color:rgba(255,255,255,0.1);
+}
+.bx-btn-group .bx-btn + .bx-btn:not(:last-child) {
+	border-radius:0px;
+	-moz-border-radius:0px;
+	-webkit-border-radius:0px;    
+}
+.bx-btn-group .bx-btn:first-child {
+    -webkit-border-top-right-radius: 0px;
+    -webkit-border-bottom-right-radius: 0px;
+    -moz-border-radius-topright: 0px;
+    -moz-border-radius-bottomright: 0px;
+    border-top-right-radius: 0px;
+    border-bottom-right-radius: 0px;
+}
+.bx-btn-group .bx-btn:last-child {
+    -webkit-border-top-left-radius: 0px;
+    -webkit-border-bottom-left-radius: 0px;
+    -moz-border-radius-topleft: 0px;
+    -moz-border-radius-bottomleft: 0px;
+    border-top-left-radius: 0px;
+    border-bottom-left-radius: 0px;
+}
+
+/* --------------- button at the end of cut string */
+
+.bx-str-limit {
+    height:10px;
+    width:14px;    
+    margin-left:3px;
+
+	border-radius:3px;
+	-moz-border-radius:3px;
+	-webkit-border-radius:3px;
+    border-collapse:separate;
+
+	-moz-box-shadow:0px 0px 1px rgba(92, 92, 92, 0.8);
+	-webkit-box-shadow:0px 0px 1px rgba(92, 92, 92, 0.8);
+    box-shadow:0px 0px 1px rgba(92, 92, 92, 0.8);
+
+    cursor:pointer;
+}
+
+/* --------------- */

+ 527 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/default.less

@@ -0,0 +1,527 @@
+@import url(_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-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-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-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-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-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);
+}
+
+/* paddings */
+.bx-def-padding {
+    .padding(@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-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);
+}
+
+/* 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;
+}
+.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);
+    color:@bx-font-color-default;
+}
+
+.bx-def-font-grayed {
+    color:@bx-font-color-grayed;
+}
+
+.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);
+}
+
+h1,
+.bx-def-font-h1 {
+    .font-size(@bx-font-size-h1);
+    font-weight:500;
+}
+
+h2,
+.bx-def-font-h2 {
+    .font-size(@bx-font-size-h2);
+    font-weight:500;
+}
+
+h3,
+.bx-def-font-h3 {
+    .font-size(@bx-font-size-h3);
+    font-weight:500;
+}
+
+/* image/thumbnail/gallery default styles */
+
+.bx-def-ava,
+.bx-def-thumb,
+.bx-def-icon {
+    .radius(80px);
+    overflow:hidden;
+}
+
+
+.bx-def-ava-size {
+    .width(@bx-size-avatar);
+    .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-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-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-info {
+    .box-sizing(border-box);
+
+    text-overflow:ellipsis;
+    overflow:hidden;
+}
+.bx-def-unit-alert-middle,
+.bx-def-unit-alert {
+    display:block;
+    position:absolute;
+    right:0;
+    bottom:0;
+    .min-width(@bx-font-size-small + 5);
+    .height(@bx-font-size-small + 5);
+    .line-height(@bx-font-size-small + 5);
+    .radius(@bx-font-size-small);
+    .border (1px, solid, #fff);
+    .font-size(@bx-font-size-small);
+    text-align:center;
+    color:white;
+}
+.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);
+    .font-size(@bx-font-size-middle);
+    .padding(3px);
+}
+
+.bx-def-gal-img-column {
+    .column-width-raw(100%);
+}
+.bx-def-gal-img-size {
+    max-width:100%;
+    height:auto;
+}
+.bx-def-gal-div-size {
+    max-width:100%;
+    .min-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();
+}
+div.bx-search-result-block[min-width~="480px"][max-width~="720px"] .bx-def-gal-unit-size {
+    width:50%;
+}
+div.bx-search-result-block[min-width~="720px"] .bx-def-gal-unit-size {
+    width:33.3%;
+}
+
+/* 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;
+}

+ 383 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/forms.css

@@ -0,0 +1,383 @@
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:first-child {
+    margin-top:0;
+}
+
+.bx-form-advanced-wrapper .bx-form-section-divider:first-child {
+    border-top:none; 
+    padding-top:0; 
+}
+
+.bx-form-advanced-wrapper .bx-form-section-divider:first-child .bx-form-section-content {
+    padding-top:0; 
+}
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:last-child .bx-form-section-content {
+    padding-bottom:0;
+}
+
+.bx-form-advanced-wrapper .bx-form-section-wrapper:first-child .bx-form-row-view-wrapper:first-child {
+    padding-top:0;
+}
+
+.bx-form-section-wrapper {
+    position:relative;
+}
+
+.bx-form-section {
+    padding:0px;
+}
+
+.bx-form-section {
+    margin:0;
+    border-width:2px;
+    border-width:0.125rem;
+    border-bottom:none;
+    border-left:none;
+    border-right:none;
+}
+
+.bx-form-section-header legend {
+    position:relative;
+    text-transform:uppercase;
+}
+
+.bx-form-section-header legend u {
+    cursor:pointer;
+}
+
+.bx-form-section-hidden .bx-form-section-content {
+    display:none;
+}
+
+.bx-form-element-wrapper:first-child {
+    margin-top:0px;
+}
+
+.bx-form-caption {
+    margin-bottom:5px;
+}
+
+.bx-form-required {
+    color:red;
+}
+
+.bx-form-warn {
+    color:red;
+    font-style:italic;
+}
+
+.bx-form-info {
+
+}
+
+/* section toggler */
+
+.bx-form-collapsable .bx-form-section-toggler {
+
+    position:absolute;
+    left:-2px;
+    top:3px;
+
+    width:16px;
+    height:16px;
+
+    text-decoration:none;
+
+    -moz-transform: rotate(90deg);
+    -webkit-transform: rotate(90deg);
+    -ms-transform: rotate(90deg);
+    -o-transform: rotate(90deg);
+
+    -webkit-transition: all 0.5s ease;
+    -moz-transition: all 0.5s ease;
+    -ms-transition: all 0.5s ease;
+    -o-transition: all 0.5s ease;
+}
+
+.bx-form-collapsed .bx-form-section-toggler {
+    -moz-transform: rotate(0deg);
+    -webkit-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+}
+
+.bx-form-section-toggler .sys-icon {
+    margin:5px 0 0 5px;
+    font-size:1.2rem;
+    line-height:1.2rem;
+}
+
+/* form inputs */
+
+.bx-form-input-slider,
+.bx-form-input-doublerange,
+.bx-form-input-select_multiple,
+.bx-form-input-select,
+.bx-form-input-radio_set,
+.bx-form-input-checkbox_set,
+.bx-form-input-number,
+.bx-form-input-datepicker,
+.bx-form-input-datetime,
+.bx-form-input-textarea,
+.bx-form-input-password,
+.bx-form-input-text {
+
+    height:36px;
+    height:2.2rem;
+    width:100%;
+    
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    padding:7px 3px 7px 3px;
+    padding:0.2rem 0.4rem;
+
+    vertical-align:middle;
+
+    border:2px solid #79bd9a;
+    border:0.125rem solid #79bd9a;
+	
+    -webkit-border-radius:0.3rem;
+    -moz-border-radius:0.3rem;	
+    border-radius:0.3rem;
+
+    outline:none;
+
+    background-color: #fff;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; 
+    filter: alpha(opacity=90); 
+    -moz-opacity: 0.9;
+    -khtml-opacity: 0.9;
+    opacity: 0.9;    
+}
+
+.bx-form-input-slider:focus,
+.bx-form-input-doublerange:focus,
+.bx-form-input-number:focus,
+.bx-form-input-datepicker:focus,
+.bx-form-input-datetime:focus,
+.bx-form-input-textarea:focus,
+.bx-form-input-password:focus,
+.bx-form-input-text:focus {
+	border-color:#6caa8a;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 
+    filter: alpha(opacity=100); 
+    -moz-opacity: 1.0;
+    -khtml-opacity: 1.0;
+    opacity: 1.0;
+
+    -webkit-transition: all 0.2s linear;
+    -moz-transition: all 0.2s linear;
+    -o-transition: all 0.2s linear;
+    transition: all 0.2s linear;
+}
+
+.bx-form-input-select_multiple,
+.bx-form-input-radio_set,
+.bx-form-input-checkbox_set {
+    height:auto;
+}
+
+.bx-form-input-textarea {
+    padding:0.5rem;
+    height:150px;
+    height:10rem;
+
+    line-height:normal;
+}
+
+.bx-form-input-datepicker {
+    width:7.5rem;
+}
+
+.bx-form-input-datetime {
+    width:10.2rem;
+}
+
+.bx-form-input-number {
+    width:6rem;
+}
+
+.bx-form-input-wrapper-slider,
+.bx-form-input-wrapper-doublerange {
+    position:relative;
+}
+
+.bx-form-input-wrapper-slider .ui-slider,
+.bx-form-input-wrapper-doublerange .ui-slider {
+    position:absolute;
+    top:0px;
+    right:0px;
+}
+
+.bx-form-input-wrapper-slider .ui-slider .ui-slider-handle,
+.bx-form-input-wrapper-doublerange .ui-slider .ui-slider-handle { 
+    z-index: 1;
+}
+
+/* switcher */
+
+.bx-switcher-cont {
+	position:relative;
+    width:60px;
+	width:3.8rem;
+    height:24px;
+	height:1.5rem;
+	overflow:hidden;
+	cursor:pointer;
+
+    background-color:#fff;
+
+    border:2px solid #79bd9a;
+    border:0.125rem solid #79bd9a;
+    -moz-border-radius: 1rem;
+    -webkit-border-radius: 1rem;
+    border-radius: 1rem;
+}
+.bx-switcher-cont input {
+    position:absolute; 
+    top:-32px;
+    top:-2rem;
+}
+.bx-switcher-canvas {
+	position:relative;
+	width:90px;
+	width:5.7rem;
+
+    -webkit-transition: left 0.2s ease-in-out;
+    -moz-transition: left 0.2s ease-in-out;
+    -ms-transition: left 0.2s ease-in-out;
+    -o-transition: left 0.2s ease-in-out;
+}
+.bx-switcher-cont.on .bx-switcher-canvas {
+	left:3px;
+	left:0.2rem;
+}
+.bx-switcher-cont.off .bx-switcher-canvas {
+	left:-34px;
+	left:-2.1rem;
+}
+.bx-switcher-on,
+.bx-switcher-off,
+.bx-switcher-handler {
+    position:absolute;	
+    top:0rem;
+	line-height:24px;
+	line-height:1.5rem;
+    color:#999;
+}
+.bx-switcher-on {
+    left:0rem;
+    width:34px;
+    width:2.1rem;
+    height:24px;
+    height:1.5rem;
+    margin-left:8px;
+    margin-left:0.5rem;
+}
+.bx-switcher-off {	
+    left:65px;
+    left:4.05rem;
+    width:34px;
+    width:2.1rem;
+    height:24px;
+    height:1.5rem;
+    margin-left:3px;
+    margin-left:0.2rem;
+}
+.bx-switcher-handler {
+    left:36px;
+    left:2.23rem;
+    top:2px;
+    top:0.13rem;
+    width:20px;
+    width:1.25rem;
+	height:20px;
+	height:1.25rem;
+	margin:0rem;
+
+    -moz-border-radius: 1rem;
+    -webkit-border-radius: 1rem;
+    border-radius: 1rem;
+
+    -webkit-transition: background-color 0.2s ease-in-out;
+    -moz-transition: background-color 0.2s ease-in-out;
+    -ms-transition: background-color 0.2s ease-in-out;
+    -o-transition: background-color 0.2s ease-in-out;
+    transition: background-color 0.2s ease-in-out;
+}
+.bx-switcher-cont.on .bx-switcher-handler {
+    background-color:#d0efa2;
+}
+.bx-switcher-cont.off .bx-switcher-handler {
+    background-color:#e7938a;
+}
+
+
+
+.bx-form-input-btn-cross {
+    position:absolute;
+    top:0px;
+    left:0px;
+    width:1.2rem;
+    height:1.2rem;
+    
+    cursor:pointer;
+}
+
+.bx-form-input-btn-cross .sys-icon {
+    line-height:1.2rem;
+    font-size:1.2rem;
+    text-decoration:none;
+}
+
+.bx-form-value-oneline,
+.bx-form-caption-oneline {
+    float:left;
+}
+
+.bx-form-caption-oneline-switcher {
+    line-height:26px;
+}
+
+.bx-form-input-wrapper-submit .bx-btn {
+    float:none;
+}
+
+.bx-form-input-wrapper-input_set .bx-btn {
+    float:left;
+}
+
+/* extra features */
+
+.bx-form-right-line-aligned {
+    float:right;
+    margin-top: 5px;
+}
+
+/* view mode */
+
+.bx-form-view-section legend {
+    padding-left:0;
+}
+
+.bx-form-row-view-wrapper > div {
+    display: inline-block;
+    white-space: normal;
+    vertical-align: bottom;
+}
+
+.bx-form-row-view-wrapper-textarea > div {
+    display: block;
+    vertical-align:top;
+}
+
+.bx-form-row-view-caption {
+    font-weight:bold;
+}
+

+ 127 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/general.css

@@ -0,0 +1,127 @@
+
+.bx-main {
+	position: relative;
+	min-height: 100%;
+	min-height: 100vh;
+}
+
+.bx-page-image-container img,
+.bx-page-html-container img {
+    max-width:100%;
+    height:auto;
+}
+.bx-page-html-container {
+    overflow:hidden;
+}
+
+/* --------------- toolbar */
+
+#bx-toolbar {
+    position:fixed;
+    width:100%;
+    height:48px;
+    height:3rem;
+
+    background-color:#3B8686;
+
+    opacity:0;
+
+    -webkit-transition: opacity .2s;
+    transition: opacity .2s;
+}
+
+#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 3rem;
+
+    line-height: 48px;
+    line-height: 3rem;
+    
+    vertical-align: middle;
+    text-align: center;
+
+    overflow:hidden;
+    text-overflow:ellipsis;
+}
+
+#bx-logo-container a {
+    text-decoration:none;
+    font-size:32px;
+    font-size:2rem;
+}  
+
+/* --------------- menu bar */
+
+#bx-menu-main-bar-wrapper {
+    padding-top:48px;
+    padding-top:3rem;
+}
+
+/* --------------- content */
+#bx-content-wrapper {
+	padding-bottom: 100px;
+	padding-bottom: 6.25rem;
+}
+
+/* --------------- footer */
+#bx-footer-wrapper {
+	position: absolute;
+
+	width: 100%;
+	height: 100px;
+	height: 6.25rem;
+	bottom: 0;
+}
+#bx-footer {
+    padding-bottom:60px;
+}
+#bx-menu-bottom {
+	float: left;
+}
+#bx-footer-copyright {
+    float:right;
+}
+
+/* --------------- social buttons */
+
+.bx-social-sharing-btn {
+    float:left;
+}
+
+.bx-social-sharing-btn a,
+.bx-social-sharing-btn .sys-icon {
+    text-decoration:none;
+    vertical-align:middle;
+}
+.bx-social-sharing-btn span {
+    font-size:1.5rem;
+    vertical-align:middle;
+    margin:0 0.2rem;
+}

+ 97 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/media-phone.css

@@ -0,0 +1,97 @@
+@media (max-width:720px) {
+
+html {
+    font-size:75%;
+}
+
+.bx-def-media-phone-hide {
+    display:none;
+}
+.bx-def-media-phone-show-block {
+    display:block;
+}
+
+
+.bx-def-constraint-all *,
+.bx-def-constraint-list ol, .bx-def-constraint-list ul,
+.bx-def-constraint-pre pre,
+.bx-def-constraint-object object,
+.bx-def-constraint-iframe iframe,
+.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:300px !important;
+    overflow:hidden !important;
+}
+.bx-def-constraint-img img {
+    height:auto !important;
+}
+.bx-def-constraint-pre pre {
+    overflow:scroll !important;
+}
+
+
+/* default.css */ 
+
+.bx-def-page-width {
+    width:auto;
+}
+
+/* common.css */
+
+/* make popup fullscreen */
+
+.bx-popup-responsive.bx-popup-trans-wrapper:not(.bx-popup-image-wrapper),
+.bx-popup-responsive:not(.bx-popup-image-wrapper) .bx-popup-trans,
+.bx-popup-responsive.bx-popup-box-wrapper,
+.bx-popup-responsive .bx-popup-box {
+    position:fixed !important;
+    width:100%;
+	width:100vw;
+    height:100%;
+    height:100vh;
+    overflow:auto;
+}
+
+.bx-popup-responsive .bx-popup-trans > div,
+.bx-popup-responsive .bx-popup-box > div {
+	border-radius:0;
+	-moz-border-radius:0;
+	-webkit-border-radius:0;
+}
+
+.bx-popup-responsive.bx-popup-image-wrapper:not(.foo) .bx-popup-border,
+.bx-popup-responsive.bx-popup-trans-wrapper:not(.foo) .bx-popup-border,
+.bx-popup-responsive.bx-popup-box-wrapper:not(.foo) .bx-popup-border {
+    border:none;
+}
+
+.bx-popup-responsive.bx-popup-image-wrapper .bx-popup,
+.bx-popup-responsive.bx-popup-trans-wrapper .bx-popup,
+.bx-popup-responsive.bx-popup-box-wrapper .bx-popup {
+	-moz-box-shadow:none;
+	-webkit-box-shadow:none;
+    box-shadow:none;
+}
+
+/* page layouts */
+
+.bx-layout-col {
+    float:none !important;
+    width:auto !important;
+}
+
+.bx-layout-cell {
+    padding:0 !important;
+}
+
+/* menu.css */
+
+ul.bx-menu-toolbar li a span {
+    display:none;
+}
+
+} /* @media end */

+ 446 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/menu.css

@@ -0,0 +1,446 @@
+/* --------------- horizontal menu */
+
+ul.bx-menu-hor {
+    padding:0;
+    margin:0;
+    list-style:none;
+}
+
+.bx-menu-hor li {
+    float:left;
+}
+
+.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, vertical lite, notifications menu */
+
+ul.bx-menu-notif,
+ul.bx-menu-verlit,
+ul.bx-menu-ver {
+    padding:0;
+    margin:0;
+    list-style:none;
+}
+
+.bx-menu-ver li {
+    margin-top:1px;
+}
+
+.bx-menu-notif li:first-child,
+.bx-menu-verlit li:first-child,
+.bx-menu-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;
+}
+
+.bx-menu-ver li img {
+    background-position:center center;
+    background-repeat:no-repeat;
+}
+
+.bx-menu-ver li .sys-icon {
+    font-size:20px;
+    font-size:1.25rem;
+}
+
+.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-menu-main-container {
+    height:40px;
+    height:2.5rem;
+    white-space: nowrap;
+    overflow:hidden;    
+}
+
+#bx-menu-main-container-scroller {
+    height:60px;
+    height:4rem;
+    overflow:auto;
+    /* -webkit-overflow-scrolling:touch; */
+}
+
+#bx-menu-main-submenu,
+#bx-menu-main {
+    display:inline-block;
+    line-height:40px;
+    line-height:2.5rem;
+    vertical-align:top;
+}
+
+#bx-menu-main-container #bx-menu-main-submenu:first-child .bx-menu-main-submenu-div:first-child {
+    display:none;
+}
+
+.bx-menu-main-submenu-div .sys-icon {
+    font-size:32px;
+    font-size:2rem;
+}
+
+ul.bx-menu-main-submenu,
+ul.bx-menu-main {    
+    padding:0;
+    margin:0;
+    list-style:none;
+    white-space:nowrap;
+}
+
+.bx-menu-main-submenu li,
+.bx-menu-main li {
+    display:inline-block;
+    margin:0;
+    vertical-align:top;
+}
+
+.bx-menu-main li a,
+.bx-menu-main li span,
+.bx-menu-main li .sys-icon,
+.bx-menu-main-submenu li a {
+    text-decoration:none;
+    font-size:16px;
+    font-size:1.2rem;
+}
+.bx-menu-main li .sys-icon {
+    font-size:32px;
+    font-size:2rem;
+}
+
+.bx-menu-main-submenu li.bx-menu-tab-active a,
+.bx-menu-main li span {
+    color:#000;
+}
+
+/* --------------- toolbar menu */
+
+#bx-menu-toolbar-1-container .bx-menu-toolbar {
+    float:left;
+}
+
+#bx-menu-toolbar-2-container .bx-menu-toolbar {
+    float:right;
+}
+
+.bx-menu-toolbar {
+    list-style:none;
+    margin:0 0 0 0;
+    padding:0;
+}
+ul.bx-menu-toolbar li {
+    float:left;
+    margin:0px;
+} 
+
+ul.bx-menu-toolbar li a {
+    position:relative;
+    display:block;
+    min-width:48px;
+    min-width:3rem;
+    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.5rem;
+}
+ul.bx-menu-toolbar li a span {
+    margin-right:0;
+}
+
+ul.bx-menu-toolbar li:first-child a {
+    margin-left:0;
+}
+
+ul.bx-menu-toolbar li img {
+    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:32px;
+    font-size:2rem;
+    text-align:center;
+    width:2rem;
+    height:2rem;
+}
+
+
+/* --------------- floating blocks menu */
+
+.bx-sliding-menu-close,
+.bx-sliding-menu-close:hover {
+    position:absolute;
+    top:0;
+    right:0;
+    text-decoration:none;
+}
+
+.bx-sliding-menu-main {
+    width:100%;
+    opacity:0.9;
+    overflow-x:auto;
+    /* -webkit-overflow-scrolling:touch; */
+}
+.bx-sliding-menu-main ul.bx-menu-floating-blocks-big {
+    display:table;
+    margin:0 auto;
+    white-space:nowrap;
+}
+
+.bx-popup-content .bx-menu-floating-blocks {
+    width:256px;
+    width:16rem;
+}
+.bx-popup-content .bx-menu-account-popup .bx-menu-floating-blocks {
+    width:auto;
+}
+
+ul.bx-menu-floating-blocks-big,
+ul.bx-menu-floating-blocks {
+    padding:0;
+    margin:0;
+    list-style:none;
+}
+
+.bx-menu-floating-blocks-big li,
+.bx-menu-floating-blocks li {
+    display: inline-block;
+    /* For IE 7 */
+    zoom: 1;
+    *display: inline;
+    vertical-align:top;    
+
+    width:64px;
+    width:4rem;
+    height:64px;
+    height:4rem;
+}
+.bx-menu-floating-blocks-big li {
+    width:128px;
+    width:8rem;
+    height:128px;
+    height:8rem;
+}
+
+.bx-menu-floating-blocks-big 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 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 li a {
+    display:block;
+    text-decoration:none;
+}
+
+.bx-menu-floating-blocks-big 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 li .sys-icon {
+    display:block;
+    width:32px;
+    width:2rem;
+    height:32px;
+    height:2rem;
+    margin:8px 16px 3px 16px;
+    margin:0.5rem 1rem 0.2rem 1rem;
+}
+.bx-menu-floating-blocks-big li img,
+.bx-menu-floating-blocks-big li .sys-icon {
+    display:block;
+    width:64px;
+    width:4rem;
+    height:64px;
+    height:4rem;
+    margin:16px 32px 8px 32px;
+    margin:1rem 2rem 0.5rem 2rem;
+}
+
+.bx-menu-floating-blocks-big 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 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-big 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;
+}
+
+/* --------------- 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-hor {
+	padding:0;
+    margin:0;
+	list-style:none;
+    text-align:center;
+}
+ul.bx-menu-btns-hor li {
+    display:inline;
+    display:inline-block;
+	vertical-align: bottom;
+}
+
+/* --------------- account popup menu */
+
+.bx-popup-content .bx-menu-account-popup {
+    min-width:17em;
+}
+
+.bx-menu-account-popup-switch-profile:hover,
+.bx-menu-account-popup-switch-profile {
+    text-decoration:none;
+}

+ 73 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/css/uploaders.css

@@ -0,0 +1,73 @@
+
+.bx-uploader-msg-err {
+    color:#900;
+}
+
+.bx-uploader-form-cont {
+    width:500px;
+}
+
+.bx-uploader-ghost-preview {
+    width:101px; 
+    float:left;
+}
+
+.bx-uploader-ghost-preview img {
+    width:101px;
+    min-height:20px;
+    background-image:url(../images/loading-spinner.gif);
+    background-repeat:no-repeat;
+    background-position:center center;
+    -webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(.7, transparent), to(rgba(0,0,0,0.4)));
+}
+
+.bx-uploader-ghost-form {
+    width:500px; 
+    float:left;
+}
+
+.bx-uploader-ghost-form-filename {
+
+}
+ 
+.bx-uploader-ghost-form-actions {
+}
+
+/* simple uploader */
+
+.bx-uploader-simple-file {
+    margin-bottom:5px;
+}
+
+/* html5 uploader */
+
+.bx-uploader-html5 { }
+
+.bx-uploader-html5-drop-area { 
+    width:auto;
+    height:200px;
+    
+    line-height:200px; 
+    text-align:center;
+
+    border:5px dashed #ccc;
+}
+
+.bx-uploader-html5-drop-area-active { border-color:#999; }
+
+.bx-uploader-html5-list { padding:0; list-style:none; }
+.bx-uploader-html5-list li { margin:0; padding:0; line-height:15px; }
+.bx-uploader-html5-file, .bx-uploader-html5-spinner, .bx-uploader-html5-size, .bx-uploader-html5-cancel, .bx-uploader-html5-failed-text {
+    margin-right: 7px;
+}
+
+.bx-uploader-html5-file {}
+.bx-uploader-html5-spinner { display:inline-block; background: url("../images/loading-spinner.gif"); width:15px; height:15px; vertical-align:text-bottom; }
+.bx-uploader-html5-size, .bx-uploader-html5-cancel {}
+
+.bx-uploader-html5-failed-text { display:none; }
+.bx-uploader-html5-fail .bx-uploader-html5-failed-text { display:inline; }
+
+.bx-uploader-html5-button input {
+    height:38px;
+}

+ 16 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_account_popup.html

@@ -0,0 +1,16 @@
+
+<div id="bx-sliding-menu-account" class="bx-sliding-menu-main bx-def-z-index-nav bx-def-color-bg-block bx-def-border-bottom bx-search-site" style="display:none; overflow:hidden; height:100%; opacity:1;">
+
+    <a class="bx-sliding-menu-close bx-def-margin bx-def-font-h1" href="javascript:void(0);" onclick="bx_menu_slide('#bx-sliding-menu-account')"><i class="sys-icon times"></i></a>
+
+    <div class="bx-menu-account-popup bx-menu-object-__object__ bx-def-padding">
+        <div class="bx-def-font-h2">
+            <span class="bx-menu-account-popup-profile">__profile_display_name__</span>
+            <a title="<bx_text_attribute:_sys_switch_profile />" class="bx-menu-account-popup-switch-profile bx-def-margin-sec-left" href="__url_switch_profile__"><i class="sys-icon exchange"></i></a>
+        </div>
+        <div class="bx-def-margin-top bx-def-margin-bottom">__menu_account__</div>
+        <div class="bx-def-color-bg-sec" style="height:0.5em; margin-left:-3em; margin-right:-3em;">1</div>
+        <div>__menu_notifications__</div>
+    </div>
+</div>
+

+ 19 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_floating_blocks.html

@@ -0,0 +1,19 @@
+<div class="bx-def-margin-sec-neg">
+    <ul class="bx-menu-floating-blocks bx-menu-object-__object__ bx-clearfix">
+        <bx_repeat:menu_items><li class="bx-def-round-corners bx-def-color-bg-hl-hover bx-def-box-sizing __class_add__">
+            <a href="__link__" target="__target__" onclick="__onclick__">
+                <div class="bx-menu-floating-blocks-icon">
+                    <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>
+                </div>
+                <div class="bx-menu-floating-blocks-title bx-def-font-small">__title__</div>
+            </a>                
+        </li></bx_repeat:menu_items>
+    </ul>
+</div>
+<script language="javascript">
+    $(document).ready(function () {
+        // add default class to active menu items
+        $('.bx-menu-floating-blocks > li.bx-menu-tab-active').not('.bx-def-color-bg-active').addClass('bx-def-color-bg-active');
+    });
+</script>

+ 19 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_floating_blocks_big.html

@@ -0,0 +1,19 @@
+
+<ul class="bx-menu-floating-blocks-big bx-menu-object-__object__ bx-clearfix">
+    <bx_repeat:menu_items><li class="bx-def-round-corners bx-def-color-bg-hl-hover bx-def-box-sizing __class_add__">
+        <a href="__link__" target="__target__" onclick="__onclick__">
+            <div class="bx-menu-floating-blocks-icon">
+                <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>
+            </div>
+            <div class="bx-menu-floating-blocks-title">__title__</div>
+        </a>                
+    </li></bx_repeat:menu_items>
+</ul>
+
+<script language="javascript">
+    $(document).ready(function () {
+        // add default class to active menu items
+        $('.bx-menu-floating-blocks-big > li.bx-menu-tab-active').not('.bx-def-color-bg-active').addClass('bx-def-color-bg-active');
+    });
+</script>

+ 5 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_main_submenu_wrapper.html

@@ -0,0 +1,5 @@
+<div id="bx-menu-main-container">
+    <div id="bx-menu-main-container-scroller">
+        __menu__
+    </div>
+</div>

+ 8 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/menu_toolbar.html

@@ -0,0 +1,8 @@
+<ul class="bx-menu-toolbar bx-menu-object-__object__">
+    <bx_repeat:menu_items>
+        <li id="bx-menu-toolbar-item-__name__" class="bx-menu-toolbar-item bx-def-color-bg-hl-hover __class_add__">
+            <a href="__link__" target="__target__" onclick="__onclick__" class="bx-def-font-contrasted"><bx_if:title><span class="bx-menu-toolbar-item-title bx-def-margin-sec-leftright">__title__</span></bx_if:title><bx_if:icon><i class="sys-icon __icon__ bx-def-margin-sec-leftright"></i></bx_if:icon><bx_if:icon-a><i class="sys-icon-a" data-rotate="down" data-icon="__icon-a__" data-color="#fff"></i></bx_if:icon-a><bx_if:image><img class="bx-def-thumb bx-def-thumb-size bx-def-margin-sec-leftright" src="__icon_url__" /></bx_if:image></a>
+            <bx_if:addon><span class="bx-menu-toolbar-item-addon bx-def-unit-alert-middle bg-col-blue2-dark">__addon__</span></bx_if:addon>
+        </li>
+    </bx_repeat:menu_items>
+</ul>

+ 12 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/popup_box.html

@@ -0,0 +1,12 @@
+<div id="__id__" class="bx-popup-wrapper bx-popup-responsive bx-popup-box-wrapper bx-def-box-sizing" style="__wrapper_style__" role="dialog" aria-describedby="__id___title">
+    <div class="bx-popup-box-pointer"><i class="bx-popup-border"></i></div>
+    <div class="bx-popup bx-popup-box bx-popup-width bx-popup-color-bg bx-popup-border bx-def-round-corners">
+    <div class="bx-def-round-corners" style="overflow:hidden;">
+        <div class="bx-popup-header bx-popup-color-bg bx-def-padding bx-def-font-contrasted">
+            <h2 id="__id___title">__title__</h2>
+            <a href="javascript:void(0);" class="bx-popup-element-close bx-def-font-h1 bx-def-font-contrasted bx-def-margin bx-def-media-desktop-hide bx-def-media-tablet-hide"><i class="sys-icon remove"></i></a>
+        </div>
+        <div class="bx-popup-content bx-def-padding">__content__</div>
+    </div>
+    </div>
+</div>

+ 9 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/popup_trans.html

@@ -0,0 +1,9 @@
+<div  id="__id__" class="bx-popup-wrapper bx-popup-responsive bx-popup-trans-wrapper bx-def-box-sizing" style="__wrapper_style__" role="alert">
+    <div class="bx-popup-box-pointer"><i class="bx-popup-border"></i></div>
+    <div class="bx-popup bx-popup-trans bx-popup-border bx-popup-color-bg bx-def-round-corners">
+        <div class="bx-popup-close-wrapper bx-def-padding bx-def-media-desktop-hide bx-def-media-tablet-hide">
+            <a href="javascript:void(0);" class="bx-popup-element-close bx-def-font-h1 bx-def-margin"><i class="sys-icon remove"></i></a>
+        </div>
+        <div class="bx-popup-content" style="">__content__</div>
+    </div>
+</div>

+ 741 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseCmts.php

@@ -0,0 +1,741 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolCmts');
+bx_import('BxDolProfile');
+bx_import('BxTemplPaginate');
+
+/**
+ * @see BxDolCmts
+ */
+class BxBaseCmts extends BxDolCmts
+{
+    protected $_sJsObjName;
+    protected $_sStylePrefix;
+
+    function __construct( $sSystem, $iId, $iInit = 1 )
+    {
+        parent::__construct( $sSystem, $iId, $iInit );
+        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';
+
+        BxDolTemplate::getInstance()->addJsTranslation('_sys_txt_cmt_loading');
+    }
+
+    /**
+     * Add comments CSS/JS
+     */
+    public function addCssJs ()
+    {
+        $oTemplate = BxDolTemplate::getInstance();
+
+        $oTemplate->addCss(array('cmts.css'));
+        $oTemplate->addJs(array('jquery.anim.js', 'jquery.form.min.js', 'BxDolCmts.js'));
+
+        bx_import('BxDolForm');
+        $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()
+    {
+        $aParams = array(
+            'sObjName' => $this->_sJsObjName,
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'sSystem' => $this->getSystemName(),
+            'iAuthorId' => $this->_getAuthorId(),
+            'iObjId' => $this->getId(),
+            'sPostFormPosition' => $this->_aSystem['post_form_position'],
+            'sBrowseType' => $this->_sBrowseType,
+            'sDisplayType' => $this->_sDisplayType
+        );
+
+        $this->addCssJs();
+        return BxDolTemplate::getInstance()->_wrapInTagJsCode("var " . $this->_sJsObjName . " = new BxDolCmts(" . json_encode($aParams) . ");");
+    }
+
+    /**
+     * get full comments block with initializations
+     */
+    function getCommentsBlock($iParentId = 0, $iVParentId = 0, $bInDesignbox = true)
+    {
+        $aBp = array('parent_id' => $iParentId, 'vparent_id' => $iVParentId);
+        $aDp = array('show_empty' => true);
+
+		//add live update
+		$sServiceCall = BxDolService::getSerializedService('system', 'get_live_updates_comments', array($this->_sSystem, $this->_iId, $this->_getAuthorId()), 'TemplCmtsServices');
+
+		bx_import('BxDolLiveUpdates');
+		BxDolLiveUpdates::getInstance()->add($this->_sSystem . '_live_updates_cmts', 1, $sServiceCall);
+		//add live update
+
+        $sCaption = _t('_cmt_block_comments_title', $this->getCommentsCount());
+        $sContent = BxDolTemplate::getInstance()->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('type' => $this->_sDisplayType, 'position' => BX_CMT_PFP_TOP)),
+            'post_form_bottom'  => $this->getFormBoxPost($aBp, array('type' => $this->_sDisplayType, 'position' => BX_CMT_PFP_BOTTOM)),
+        	'view_image_popup' => $this->_getViewImagePopup(),
+            'script' => $this->getJsScript()
+        ));
+
+        return $bInDesignbox ? DesignBoxContent($sCaption, $sContent, BX_DB_PADDING_DEF, $this->_getControlsBox()) : array(
+            'title' => $sCaption,
+            'content' => $sContent,
+            'designbox_id' => BX_DB_PADDING_DEF,
+            '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))
+            return isset($aDp['show_empty']) && $aDp['show_empty'] === true ? $this->_getEmpty() : '';
+            
+        $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())
+    {
+        return BxDolTemplate::getInstance()->parseHtmlByName('comment_block.html', array(
+            'system' => $this->_sSystem,
+            'id' => $this->getId(),
+            'comment' => $this->getComment(
+        		$iCmtId, 
+        		array_merge(array('type' => $this->_sBrowseType), $aBp), 
+        		array_merge(array('type' => BX_CMT_DISPLAY_THREADED), $aDp)
+        	),
+        	'view_image_popup' => $this->_getViewImagePopup(), 
+            'script' => $this->getJsScript()
+        ));
+    }
+
+    /**
+     * get one just posted comment
+     *
+     * @param  int    $iCmtId - comment id
+     * @return string
+     */
+    function getComment($mixedCmt, $aBp = array(), $aDp = array())
+    {
+        $oTemplate = BxDolTemplate::getInstance();
+
+        $iUserId = $this->_getAuthorId();
+        $aCmt = !is_array($mixedCmt) ? $this->getCommentRow((int)$mixedCmt) : $mixedCmt;
+
+        list($sAuthorName, $sAuthorLink, $sAuthorIcon) = $this->_getAuthorInfo($aCmt['cmt_author_id']);
+
+        $sClass = '';
+        if(isset($aCmt['vote_rate']) && (float)$aCmt['vote_rate'] < $this->_aSystem['viewing_threshold']) {
+            $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 = ' cmt-hidden';
+        }
+
+        if($aCmt['cmt_author_id'] == $iUserId)
+            $sClass .= ' cmt-mine';
+
+        $sActions = $this->_getActionsBox($aCmt, $aDp);
+
+        $sText = $aCmt['cmt_text'];
+        $sTextMore = '';
+
+        $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);
+        $sTextMore = $this->_prepareTextForOutput($sTextMore);
+
+        $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->getBaseUrl() . '#' . $this->_sSystem . $aCmt['cmt_parent_id'],
+                'par_cmt_author' => $sParAuthorName
+            );
+        }
+
+        $aTmplImages = array();
+        if($this->isAttachImageEnabled()) {
+            $aImages = $this->_oQuery->getImages($this->_aSystem['system_id'], $aCmt['cmt_id']);
+            if(!empty($aImages) && is_array($aImages)) {
+                bx_import('BxDolStorage');
+        		$oStorage = BxDolStorage::getObjectInstance($this->getStorageObjectName());
+
+            	bx_import('BxDolImageTranscoder');
+                $oTranscoder = BxDolImageTranscoder::getObjectInstance($this->getTranscoderPreviewName());
+
+                foreach($aImages as $aImage)
+                    $aTmplImages[] = array(
+                        'style_prefix' => $this->_sStylePrefix,
+                        'js_object' => $this->_sJsObjName,
+                        'image' => $oTranscoder->getImageUrl($aImage['image_id']),
+                    	'image_orig' => $oStorage->getFileUrlById($aImage['image_id'])
+                    );
+            }
+        }
+
+        $sReplies = '';
+        if((int)$aCmt['cmt_replies'] > 0 && !empty($aDp) && $aDp['type'] == BX_CMT_DISPLAY_THREADED)
+            $sReplies = $this->getComments(array('parent_id' => $aCmt['cmt_id'], 'vparent_id' => $aCmt['cmt_id'], 'type' => $aBp['type']), $aDp);
+
+        $bAuthorIcon = !empty($sAuthorIcon);
+        return $oTemplate->parseHtmlByName('comment.html', array(
+            'system' => $this->_sSystem,
+            'style_prefix' => $this->_sStylePrefix,
+            'js_object' => $this->_sJsObjName,
+            'id' => $aCmt['cmt_id'],
+            'class' => $sClass,
+            '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_name' => $sAuthorName
+                )
+            ),
+            'bx_if:show_author_text' => array(
+                'condition' => empty($sAuthorLink),
+                'content' => array(
+                    'author_name' => $sAuthorName
+                )
+            ),
+            'bx_if:show_reply_to' => array(
+                'condition' => !empty($aTmplReplyTo),
+                'content' => $aTmplReplyTo
+            ),
+            'text' => $sText,
+            'bx_if:show_more' => array(
+                'condition' => !empty($sTextMore),
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'js_object' => $this->_sJsObjName,
+                    'text_more' => $sTextMore
+                )
+            ),
+            'bx_if:show_attached' => array(
+                'condition' => !empty($aTmplImages),
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'bx_repeat:attached' => $aTmplImages
+                )
+            ),
+            'actions' => $sActions,
+            'replies' =>  $sReplies
+        ));
+    }
+
+	/**
+     * 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']);
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('comment_live_search.html', array(
+            'bx_if:show_icon' => array(
+                'condition' => $bAuthorIcon,
+                'content' => array(
+                    'author_icon' => $sAuthorIcon,
+        			'view_link' => $sViewLink
+                )
+            ),
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bAuthorIcon,
+                'content' => array()
+            ),
+            'view_link' => $sViewLink,
+            'text' => BxTemplFunctions::getInstance()->getStringWithLimitedLength(strip_tags($aCmt['cmt_text']), $this->_sSnippetLenthLiveSearch),
+            'sample' => isset($aParams['txt_sample_single']) ? _t($aParams['txt_sample_single']) : ''
+        ));
+    }
+
+    function getFormBoxPost($aBp = array(), $aDp = array())
+    {
+        return $this->_getFormBox(BX_CMT_ACTION_POST, $aBp, $aDp);
+    }
+
+    function getFormBoxEdit($aBp = array(), $aDp = array())
+    {
+        return $this->_getFormBox(BX_CMT_ACTION_EDIT, $aBp, $aDp);
+    }
+
+    function getFormPost($iCmtParentId = 0)
+    {
+        return $this->_getFormPost($iCmtParentId);
+    }
+
+    function getFormEdit($aCmt)
+    {
+        return $this->_getFormEdit($aCmt);
+    }
+
+    function getNotification($iCount = 0)
+    {
+    	bx_import('BxDolTemplate');
+    	$sContent = BxDolTemplate::getInstance()->parseHtmlByName('comments_notification.html', array(
+    		'html_id' => 'cmts-notification-' . $this->_sSystem . '-' + $this->_iId,
+			'style_prefix' => $this->_sStylePrefix,
+    		'url' => $this->getBaseUrl(),
+			'message' => _t('_cmt_txt_n_new_comments', $iCount)
+		));
+
+		return $sContent;
+    }
+    /**
+     * private functions
+     */
+    protected function _getControlsBox()
+    {
+        $oTemplate = BxDolTemplate::getInstance();
+
+        $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\');')
+            );
+
+            bx_import('BxTemplMenuInteractive');
+            $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\');'),
+            );
+
+            bx_import('BxTemplMenuInteractive');
+            $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 = $bBrowseType;
+        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 $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())
+    {
+    	$oTemplate = BxDolTemplate::getInstance();
+
+    	$bViewOnly = isset($aDp['view_only']) && $aDp['view_only'] === true;
+
+        $bMenuManage = false;
+        $sMenuActions = $sMenuManage = '';
+
+		if(!$bViewOnly) {
+	        //--- Actions Menu
+	        bx_import('BxDolMenu');
+	        $oMenuActions = BxDolMenu::getObjectInstance($this->_sMenuObjActions);
+	        $oMenuActions->setCmtsData($this, $aCmt['cmt_id']);
+	        $sMenuActions = $oMenuActions->getCode();
+
+	        $oVote = $this->getVoteObject($aCmt['cmt_id']);
+	        if($oVote !== false)
+	            $sMenuActions .= $oVote->getJsScript();
+	
+	        //--- Manage Menu
+	        $oMenuManage = BxDolMenu::getObjectInstance($this->_sMenuObjManage);
+	        $oMenuManage->setCmtsData($this, $aCmt['cmt_id']);
+	
+	        $sMenuManage = $oMenuManage->getCode();
+	        $bMenuManage = !empty($sMenuManage);
+	        if($bMenuManage) {
+	            $sMenuManage = $oTemplate->parseHtmlByName('comment_manage.html', array(
+	                'style_prefix' => $this->_sStylePrefix,
+	                'content' => $sMenuManage
+	            ));
+	
+	            bx_import('BxTemplFunctions');
+	            $sMenuManage = BxTemplFunctions::getInstance()->transBox($this->_sSystem . '-manage-' . $aCmt['cmt_id'], $sMenuManage, true);
+	        }
+		}
+
+        $sAgo = bx_time_js($aCmt['cmt_time']);
+        $bObjectTitle = !empty($this->_aSystem['trigger_field_title']);
+        return $oTemplate->parseHtmlByName('comment_actions.html', array(
+            'id' => $aCmt['cmt_id'],
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:show_ago_link' => array(
+                'condition' => $bObjectTitle,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+                    'view_link' => $this->getViewUrl($aCmt['cmt_id']),
+                    'ago' => $sAgo
+                )
+            ),
+            'bx_if:show_ago_text' => array(
+                'condition' => !$bObjectTitle,
+                'content' => array(
+                    'ago' => $sAgo
+                )
+            ),
+            'menu_actions' => $sMenuActions,
+/*
+            'bx_if:hide_rate_count' => array(
+                'condition' => (int)$aCmt['cmt_rate'] <= 0,
+                'content' => array()
+            ),
+            'points' => _t(in_array($aCmt['cmt_rate'], array(-1, 0, 1)) ? '_N_point' : '_N_points', $aCmt['cmt_rate']),
+*/
+            'bx_if:show_menu_manage' => array(
+                'condition' => $bMenuManage,
+                'content' => array(
+                    'js_object' => $this->_sJsObjName,
+                    'style_prefix' => $this->_sStylePrefix,
+                    'id' => $aCmt['cmt_id'],
+                    'popup_text' => $sMenuManage
+                )
+            )
+        ));
+    }
+
+    protected function _getFormBox($sType, $aBp, $aDp)
+    {
+        $iCmtParentId = isset($aBp['parent_id']) ? (int)$aBp['parent_id'] : 0;
+        $sPosition = isset($aDp['position']) ? $aDp['position'] : '';
+
+        if(!$this->isPostReplyAllowed())
+            return '';
+
+        $sPositionSystem = $this->_aSystem['post_form_position'];
+        if(!empty($sPosition) && $sPositionSystem != $sPosition)
+            return '';
+
+        $sMethod = '_getForm' . ucfirst($sType);
+        $aForm = $this->$sMethod($iCmtParentId);
+
+        return BxDolTemplate::getInstance()->parseHtmlByName('comment_reply_box.html', array(
+            'js_object' => $this->_sJsObjName,
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_if:show_class' => array(
+                'condition' => !empty($sPosition),
+                'content' => array(
+                    'class' => $this->_sStylePrefix . '-reply-' . $sPosition
+                )
+            ),
+            'form' => $aForm['form'],
+            'form_id' => $aForm['form_id'],
+        ));
+    }
+
+    protected function _getFormPost($iCmtParentId = 0)
+    {
+        $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');
+            $sCmtText = $this->_prepareTextForSave ($sCmtText);
+            $oForm->setSubmittedValue('cmt_text', $sCmtText, $oForm->aFormAttrs['method']);
+
+            $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)) {
+                        bx_import('BxDolStorage');
+                        $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);
+
+                    $this->_sendNotificationEmail($iCmtId, $iCmtParentId);
+                }
+
+                $this->_triggerComment();
+
+                $this->isPostReplyAllowed(true);
+
+                bx_import('BxDolAlerts');
+                $oZ = new BxDolAlerts($this->_sSystem, 'commentPost', $this->getId(), $iCmtAuthorId, array('comment_id' => $iCmtId, 'comment_author_id' => $iCmtAuthorId));
+                $oZ->alert();
+
+                return array('id' => $iCmtId, 'parent_id' => $iCmtParentId);
+            }
+
+            return array('msg' => _t('_cmt_err_cannot_perform_action'));
+        }
+
+        return array('form' => $oForm->getCode(), 'form_id' => $oForm->id);
+    }
+
+    protected function _getFormEdit($iCmtId)
+    {
+        $aCmt = $this->_oQuery->getCommentSimple ($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']);
+        $aCmt['cmt_text'] = $this->_prepareTextForEdit($aCmt['cmt_text']);
+
+        $oForm->initChecker($aCmt);
+        if($oForm->isSubmittedAndValid()) {
+            $sCmtText = $oForm->getCleanValue('cmt_text');
+            $sCmtText = $this->_prepareTextForSave ($sCmtText);
+            $oForm->setSubmittedValue('cmt_text', $sCmtText, $oForm->aFormAttrs['method']);
+
+            if($oForm->update($iCmtId)) {
+                $this->isEditAllowed(true);
+
+                bx_import('BxDolAlerts');
+                $oZ = new BxDolAlerts($this->_sSystem, 'commentUpdated', $this->getId(), $iCmtAuthorId, array('comment_id' => $aCmt['cmt_id'], 'comment_author_id' => $aCmt['cmt_author_id']));
+                $oZ->alert();
+
+                return array('id' => $iCmtId, 'text' => $sCmtText);
+            }
+
+            return array('msg' => _t('_cmt_err_cannot_perform_action'));
+        }
+
+        return array('form' => $oForm->getCode(), 'form_id' => $oForm->id);
+    }
+
+    protected function _getForm($sAction, $iId)
+    {
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->setId(sprintf($oForm->aFormAttrs['id'], $sAction, $this->_sSystem, $iId));
+        $oForm->setName(sprintf($oForm->aFormAttrs['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'])) {
+            $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 = BxDolTemplate::getInstance()->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()
+    {
+        return BxDolTemplate::getInstance()->parseHtmlByName('comment_empty.html', array(
+            'style_prefix' => $this->_sStylePrefix
+        ));
+    }
+
+    protected function _getViewImagePopup()
+    {
+    	$sViewImagePopupId = 'cmts-box-' . $this->_sSystem . '-' . $this->getId() . '-view-image-popup' ;
+        $sViewImagePopupContent = BxDolTemplate::getInstance()->parseHtmlByName('popup_image.html', array(
+    		'image_url' => ''
+    	));
+
+    	bx_import('BxTemplFunctions');
+    	return BxTemplFunctions::getInstance()->transBox($sViewImagePopupId, $sViewImagePopupContent, true);
+    }
+    protected function _echoResultJson($a, $isAutoWrapForFormFileSubmit = false)
+    {
+
+        header('Content-type: text/html; charset=utf-8');
+
+        $s = json_encode($a);
+        if ($isAutoWrapForFormFileSubmit && !empty($_FILES))
+            $s = '<textarea>' . $s . '</textarea>'; // http://jquery.malsup.com/form/#file-upload
+        echo $s;
+    }
+}
+
+/** @} */

+ 49 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseCmtsServices.php

@@ -0,0 +1,49 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+/**
+ * System services related to Comments.
+ */
+class BxBaseCmtsServices extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceGetMenuItemAddonVote($sSystem, $iId, $iCmtId)
+    {
+        bx_import('BxDolCmts');
+        $oCmts = BxDolCmts::getObjectInstance($sSystem, $iId);
+
+        $oVote = $oCmts->getVoteObject($iCmtId);
+        if($oVote !== false)
+            return $oVote->getCounter();
+
+        return '';
+    }
+
+    public function serviceGetLiveUpdatesComments($sSystem, $iContentId, $iProfileId)
+    {
+    	bx_import('BxDolCmts');
+        $oCmts = BxDolCmts::getObjectInstance($sSystem, $iContentId);
+        if(!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+    	return array(
+    		'count' => $oCmts->getCommentsCount($iContentId, 0, BX_CMT_FILTER_OTHERS), // required
+    		'method' => $oCmts->getJsObjectName() . '.showLiveUpdate(oData)', // required
+    		'data' => array(
+    			'code' => $oCmts->getNotification()
+    		),  // optional, may have some additional data to be passed in JS method provided using 'method' param above.
+    	);
+    }
+}
+
+/** @} */

+ 96 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseConfig.php

@@ -0,0 +1,96 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+class BxBaseConfig extends BxDol implements iBxDolSingleton
+{
+    protected $_aConfig = array (
+        'bAllowUnicodeInPreg' => false, ///< allow unicode in regular expressions
+        'aLessConfig' => array (
+            'bx-page-width' => '1000px',
+
+            'bx-margin' => '20px',
+            'bx-margin-sec' => '10px',
+            'bx-margin-thd' => '5px',
+
+            'bx-padding' => '20px',
+            'bx-padding-sec' => '10px',
+            'bx-padding-thd' => '5px',
+
+            'bx-font-family' => '"Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif',
+
+            'bx-size-avatar' => '96px',
+            'bx-size-thumb' => '48px',
+            'bx-size-icon' => '32px',
+
+            'bx-size-gallery-img-width' => '300px',
+            'bx-size-gallery-img-height' => '200px',
+            'bx-size-gallery-unit-width' => '300px',
+            'bx-size-gallery-unit-height' => '320px',
+
+            'bx-color-page' => '#fff',
+            'bx-color-block' => '#fff',
+            'bx-color-box' => '#f2f2f2',
+            'bx-color-sec' => '#f2f2f2',
+            'bx-color-hl' => 'rgba(196, 248, 156, 0.2)',
+            'bx-color-active' => 'rgba(196, 248, 156, 0.4)',
+
+            'bx-border-width' => '1px',
+            'bx-border-type' => 'solid',
+            'bx-border-color' => '#d0d0d0',
+
+            'bx-font-size-default' => '18px',
+            'bx-font-size-small' => '14px',
+            'bx-font-size-middle' => '16px',
+            'bx-font-size-large' => '22px',
+            'bx-font-size-h1' => '28px',
+            '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',
+        ),
+    );
+
+    function __construct()
+    {
+        parent::__construct();
+
+        if (class_exists('BxDolDb') && BxDolDb::getInstance())
+            $this->_aConfig['aLessConfig']['bx-page-width'] = getParam('main_div_width');
+    }
+
+    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]);
+    }
+
+}
+
+/** @} */

+ 41 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseLiveUpdates.php

@@ -0,0 +1,41 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolLiveUpdates');
+
+/**
+ * @see BxDolLiveUpdates
+ */
+class BxBaseLiveUpdates extends BxDolLiveUpdates
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+	public function init($aParams = array())
+    {
+        $aParams = array_merge(array(
+        	'sActionsUrl' => BX_DOL_URL_ROOT . 'live_updates.php',
+        	'sObjName' => $this->_sJsObject,
+        	'iInterval' => $this->_iInterval,
+        	'bServerRequesting' => !empty($this->_aSystems)
+        ), $aParams);
+
+		$sContent = "var " . $this->_sJsObject . " = new " . $this->_sJsClass . "(" . json_encode($aParams) . ");";
+
+		bx_import('BxDolTemplate');
+		$oTemplate = BxDolTemplate::getInstance();
+
+		$oTemplate->addJs(array('BxDolLiveUpdates.js'));
+        return $oTemplate->_wrapInTagJsCode($sContent);
+    }
+}
+
+/** @} */

+ 27 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseLiveUpdatesServices.php

@@ -0,0 +1,27 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+/**
+ * System services related to Live Updates.
+ */
+class BxBaseLiveUpdatesServices extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceInit()
+    {
+        bx_import('BxDolLiveUpdates');
+        return BxDolLiveUpdates::getInstance()->init();
+    }
+}
+
+/** @} */

+ 178 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenu.php

@@ -0,0 +1,178 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolMenu');
+bx_import('BxDolPaginate');
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseMenu extends BxDolMenu
+{
+    protected $_oTemplate;
+    protected $_aOptionalParams = array('target' => '', 'onclick' => '');
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+    }
+
+    /**
+     * Get menu code.
+     * @return string
+     */
+    public function getCode ()
+    {
+        $sMenuTitle = isset($this->_aObject['title']) ? _t($this->_aObject['title']) : 'Menu-' . rand(0, PHP_INT_MAX);
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->beginMenu($sMenuTitle);
+
+        $s = '';
+        $aVars = $this->_getTemplateVars ();
+        if (!empty($aVars['bx_repeat:menu_items'])) {
+            $this->_addJsCss();
+            $s = $this->_oTemplate->parseHtmlByName($this->_aObject['template'], $aVars);
+        }
+
+        if (isset($GLOBALS['bx_profiler'])) $GLOBALS['bx_profiler']->endMenu($sMenuTitle);
+
+        return $s;
+    }
+
+    /**
+     * Get template variables array
+     * @return array
+     */
+    protected function _getTemplateVars ()
+    {
+        return array (
+            'object' => $this->_sObject,
+            'bx_repeat:menu_items' => $this->getMenuItems (),
+        );
+    }
+
+    /**
+     * Get menu items array, which are ready to pass to template.
+     * @return array
+     */
+    public function getMenuItems ()
+    {
+        if (!isset($this->_aObject['menu_items']))
+            $this->_aObject['menu_items'] = $this->_oQuery->getMenuItems();
+
+		$aItems = array();
+        foreach ($this->_aObject['menu_items'] as $aItem) {
+        	$aItem = $this->_getMenuItem ($aItem);
+        	if($aItem !== false)
+            	$aItems[] = $aItem;
+        }
+
+        return $aItems;
+    }
+
+	protected function _getMenuItem ($a)
+	{
+		if (isset($a['active']) && !$a['active'])
+			return false;
+
+		if (isset($a['visible_for_levels']) && !$this->_isVisible($a))
+        	return false;
+
+		$a['title'] = _t($a['title']);
+
+        $this->removeMarker('addon');
+
+		$a = $this->_replaceMarkers($a);
+
+		$mixedAddon = $this->_getMenuAddon($a);
+		$this->addMarkers(array('addon' => $mixedAddon));
+
+		$a = $this->_replaceMarkers($a);
+
+		list ($sIcon, $sIconUrl, $sIconA) = $this->_getMenuIcon($a);
+
+		$a['class_add'] = $this->_isSelected($a) ? 'bx-menu-tab-active' : '';
+		$a['link'] = isset($a['link']) ? $this->_oPermalinks->permalink($a['link']) : 'javascript:void(0);';
+		$a['title_attr'] = bx_html_attribute($a['title']);
+		$a['bx_if:image'] = array (
+			'condition' => (bool)$sIconUrl,
+			'content' => array('icon_url' => $sIconUrl),
+		);
+		$a['bx_if:icon'] = array (
+        	'condition' => (bool)$sIcon,
+            'content' => array('icon' => $sIcon),
+		);
+		$a['bx_if:icon-a'] = array (
+        	'condition' => (bool)$sIconA,
+            'content' => array('icon-a' => $sIconA),
+		);
+		$a['bx_if:title'] = array (
+			'condition' => (bool)$a['title'],
+			'content' => array('title' => $a['title']),
+		);
+		$a['bx_if:addon'] = array (
+			'condition' => (bool)$mixedAddon,
+			'content' => array('addon' => $mixedAddon),
+		);
+
+		foreach ($this->_aOptionalParams as $sName => $sDefaultValue)
+        	if (!isset($a[$sName]))
+            	$a[$sName] = $sDefaultValue;
+
+		return $a;
+	}
+
+    protected function _getMenuIcon ($a)
+    {
+        $sIcon = false;
+        $sIconA = false;
+        $sIconUrl = false;
+        if (!empty($a['icon'])) {
+            if ((int)$a['icon'] > 0 ) {
+                bx_import('BxDolStorage');
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+                $sIconUrl = $oStorage ? $oStorage->getFileUrlById((int)$a['icon']) : false;
+            } else {
+                if (false === strpos($a['icon'], '.')) { 
+                    if (0 === strncmp($a['icon'], 'a:', 2))
+                        $sIconA = substr($a['icon'], 2); // animated icon
+                    else
+                        $sIcon = $a['icon']; // font icons
+                } else {
+                    $sIconUrl = $this->_oTemplate->getIconUrl($a['icon']);
+                }
+            }
+        }
+        return array ($sIcon, $sIconUrl, $sIconA);
+    }
+
+    protected function _getMenuAddon ($aMenuItem)
+    {
+        if (empty($aMenuItem['addon']))
+            return '';
+
+        return BxDolService::callSerialized($aMenuItem['addon'], $this->_aMarkers);
+    }
+
+    /**
+     * Add css/js files which are needed for menu display and functionality.
+     */
+    protected function _addJsCss()
+    {
+        $this->_oTemplate->addCss('menu.css');
+    }
+
+}
+
+/** @} */

+ 108 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuSetAclLevel.php

@@ -0,0 +1,108 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxTemplMenu');
+
+/**
+ * Menu to set acl level for profile
+ */
+class BxBaseMenuSetAclLevel extends BxTemplMenu
+{
+	protected $mixedProfileId;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    public function getCode ($mixedProfileId = 0)
+    {
+    	$this->mixedProfileId = $mixedProfileId;
+
+        if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && ($mixedProfileId = bx_get('profile_id', 'post')) && ($iAclLevelId = bx_get('acl_level_id', 'post'))) {
+			$mixedProfileId = urldecode($mixedProfileId);
+			if(!is_numeric($mixedProfileId))
+				$mixedProfileId = unserialize($mixedProfileId);
+
+            header('Content-type: text/html; charset=utf-8');
+            echo $this->setMembership($mixedProfileId, $iAclLevelId);
+            exit;
+        }
+
+        return parent::getCode ();
+    }
+
+    public function getMenuItems ()
+    {
+        $this->loadData();
+        return parent::getMenuItems();
+    }
+
+    protected function loadData()
+    {
+    	$mixedProfileId = $this->mixedProfileId;
+    	if(bx_get('profile_id') !== false)
+        	$mixedProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+
+        bx_import('BxDolAcl');
+        $oAcl = BxDolAcl::getInstance();
+        if (!$oAcl)
+            return;
+
+		$bBulk = is_array($mixedProfileId);
+		if(!$bBulk) {
+			$aProfileAclLevel = $oAcl->getMemberMembershipInfo($mixedProfileId);
+			$this->setSelected('', $aProfileAclLevel['id']);
+		}
+
+		$aAclLevels = $oAcl->getMemberships(false, true);
+
+        $aItems = array();
+        foreach ($aAclLevels as $iId => $sTitle) {
+            $aItems[] = array(
+                'id' => $iId,
+                'name' => $iId,
+                'class' => '',
+                'title' => $sTitle,
+                'icon' => '',
+                'link' => 'javascript:void(0);',
+                'onclick' => "bx_set_acl_level(" . (!$bBulk ? $mixedProfileId : "'" . urlencode(serialize($mixedProfileId)) . "'") . ", {$iId}, '.bx-popup-applied');"
+            );
+        }
+
+        $this->_aObject['menu_items'] = $aItems;
+    }
+
+    protected function setMembership($mixedProfileId, $iAclLevelId)
+    {
+        bx_import('BxDolAcl');
+
+        if(!is_array($mixedProfileId))
+        	$mixedProfileId = array($mixedProfileId);
+
+		$iPerformerId = bx_get_logged_profile_id();
+        $aCheck = checkActionModule($iPerformerId, 'set acl level', 'system', false);
+        if(!isAdmin() && $aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+			return $aCheck[CHECK_ACTION_MESSAGE];
+
+		$iSet = 0;
+		$oAcl = BxDolAcl::getInstance();
+		foreach($mixedProfileId as $iProfileId) {
+        	if(!$oAcl->setMembership($iProfileId, $iAclLevelId, 0, true))
+        		continue;
+
+			$iSet += 1;
+			checkActionModule($iPerformerId, 'set acl level', 'system', true); // perform action
+		}
+
+		return count($mixedProfileId) != $iSet ? _t('_error occured') : '';
+    }
+}
+
+/** @} */

+ 29 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuSite.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxTemplMenu');
+
+/**
+ * Site main menu representation.
+ */
+class BxBaseMenuSite extends BxTemplMenu
+{
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    public function getCode ()
+    {
+        $s = parent::getCode ();
+        return '<div id="bx-sliding-menu-' . $this->_sObject . '" class="bx-sliding-menu-main bx-def-z-index-nav bx-def-color-bg-block bx-def-border-bottom" style="display:none;">' . $s . '</div>';
+    }
+}
+
+/** @} */

+ 111 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuSubmenu.php

@@ -0,0 +1,111 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxTemplMenu');
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseMenuSubmenu extends BxTemplMenu
+{
+
+    protected $_sObjectSubmenu = false;
+    protected $_mixedMainMenuItemSelected = false;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    /**
+     * Set current submenu object
+     * @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;
+    }
+
+    /**
+     * Get menu code.
+     * @return string
+     */
+    public function getCode ()
+    {
+        $aMenuItemSelected = $this->_getSelectedMenuItem ();
+//        if (!$aMenuItemSelected)
+//            return false;
+
+        if (!$this->_sObjectSubmenu && $aMenuItemSelected['submenu_object'])
+            $this->_sObjectSubmenu = $aMenuItemSelected['submenu_object'];
+
+        bx_import('BxDolPermalinks');
+        $oPermalinks = BxDolPermalinks::getInstance();
+
+        $aVars = array (
+            'object' => $this->_sObject,
+            'title' => $aMenuItemSelected['title'],
+            'link' => $oPermalinks->permalink($aMenuItemSelected['link']),
+            'icon' => $aMenuItemSelected['icon'],
+        );
+
+        $this->_addJsCss();
+
+        $oSubmenu = null;
+        if ($this->_sObjectSubmenu)
+            $oSubmenu = BxDolMenu::getObjectInstance($this->_sObjectSubmenu);
+
+        if (!$oSubmenu)
+            return '';
+
+        $sMenu = $this->_oTemplate->parseHtmlByName($this->_aObject['template'], $aVars) . $oSubmenu->getCode();
+
+        $aVars = array('menu' => $sMenu);
+        return $this->_oTemplate->parseHtmlByName('menu_main_submenu_wrapper.html', $aVars);
+    }
+
+    protected 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;
+    }
+}
+
+/** @} */

+ 60 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseMenuToolbar.php

@@ -0,0 +1,60 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxTemplMenu');
+
+/**
+ * Menu representation.
+ * @see BxDolMenu
+ */
+class BxBaseMenuToolbar extends BxTemplMenu
+{
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    public function getMenuItems ()
+    {
+        $a = parent::getMenuItems ();
+
+        if (!isLogged())
+            return $a;
+
+        foreach ($a as $k => $r) {
+            if ('account' != $r['name'])
+                continue;
+
+            bx_import('BxDolProfile');
+            $oProfile = BxDolProfile::getInstance(bx_get_logged_profile_id ());
+            $sUrlIcon = $oProfile->getThumb();
+            if (!$sUrlIcon)
+                break;
+
+            $a[$k]['bx_if:image'] = array (
+                'condition' => true,
+                'content' => array('icon_url' => $sUrlIcon),
+            );
+            $a[$k]['bx_if:icon']['condition'] = false;
+        }
+
+        return $a;
+    }
+
+    protected function _getMenuItem ($a)
+    {
+        $a = parent::_getMenuItem ($a);
+        if ('add-content' == $a['name'] || 'search' == $a['name'])
+            $a['class_add'] .= ' bx-def-media-phone-hide';
+        return $a;
+    }
+
+}
+
+/** @} */

+ 23 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBasePageDashboard.php

@@ -0,0 +1,23 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxTemplPage');
+
+/**
+ * Dashboard page.
+ */
+class BxBasePageDashboard extends BxTemplPage
+{
+    public function __construct($aObject, $oTemplate)
+    {
+        parent::__construct($aObject, $oTemplate);
+    }
+}
+
+/** @} */

+ 98 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseUploaderHTML5.php

@@ -0,0 +1,98 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import ('BxDolUploader');
+
+/**
+ * Upload files using AJAX uploader with multiple files selection support (without flash),
+ * it works in Firefox and WebKit(Safari, Chrome) browsers only, but has fallback for other browsers (IE, Opera).
+ * @see BxDolUploader
+ */
+class BxBaseUploaderHTML5 extends BxDolUploader
+{
+    protected $_sDivId; ///< div id where upload button will be placed
+
+    function __construct ($aObject, $sStorageObject, $sUniqId)
+    {
+        parent::__construct($aObject, $sStorageObject, $sUniqId);
+
+        $this->_sDivId = 'bx-form-input-files-' . $sUniqId . '-div-' . $this->_aObject['object'];
+        $this->_sButtonTemplate = 'uploader_button_html5.html';
+    }
+
+    /**
+     * add necessary js, css files and js translations
+     */ 
+    public function addCssJs ()
+    {
+        parent::addCssJs ();
+        $this->_oTemplate->addJs('fileuploader.js');
+    }
+
+    /**
+     * Get uploader button title
+     */
+    public function getUploaderButtonTitle($mixed = false)
+    {
+        if (is_string($mixed))
+            return $mixed;
+        elseif (is_array($mixed) && isset($mixed['HTML5']))
+            return $mixed['HTML5'];
+        else
+            return _t('_sys_uploader_html5_button_name');
+    }
+
+    /**
+     * Show uploader form.
+     * @return HTML string
+     */
+    public function getUploaderForm($isMultiple = true, $iContentId = false)
+    {
+        return $this->_oTemplate->parseHtmlByName('uploader_form_html5.html', array(
+            'form_container_id' => $this->_sFormContainerId,
+            'errors_container_id' => $this->_sErrorsContainerId,
+            'uploader_instance_name' => $this->getNameJsInstanceUploader(),
+            'restrictions_text' => $this->getRestrictionsText(),
+            'div_id' => $this->_sDivId,
+            'content_id' => $iContentId,
+        ));
+    }
+
+    /**
+     * Handle uploads here.
+     * @param $mixedFiles as usual $_FILES['some_name'] array, but maybe some other params depending on the uploader
+     * @return nothing, but if some files failed to upload, the actual error message can be determined by calling BxDolUploader::getUploadErrorMessages()
+     */
+    public function handleUploads ($iProfileId, $mixedFiles, $isMultiple = true, $iContentId = false)
+    {
+        bx_import('BxDolStorage');
+        $oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
+
+        $iProfileId = bx_get_logged_profile_id();
+
+        if (!$isMultiple)
+            $this->cleanupGhostsForProfile($iProfileId, $iContentId);
+
+        if (bx_get('qqfile'))
+            $iId = $oStorage->storeFileFromXhr(bx_get('qqfile'), true, $iProfileId, $iContentId);
+        else
+            $iId = $oStorage->storeFileFromForm($_FILES['qqfile'], true, $iProfileId, $iContentId);
+
+        if ($iId) {
+            $aResponse = array ('success' => 1);
+        } else {
+            $this->appendUploadErrorMessage(_t('_sys_uploader_err_msg', isset($_FILES['qqfile']['name']) ? $_FILES['qqfile']['name'] : bx_get('qqfile'), $oStorage->getErrorString()));
+            $aResponse = array ('error' => $this->getUploadErrorMessages());
+        }
+
+        echo htmlspecialchars(json_encode($aResponse), ENT_NOQUOTES);
+    }
+}
+
+/** @} */

+ 62 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/scripts/BxBaseUploaderSimple.php

@@ -0,0 +1,62 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import ('BxDolUploader');
+
+/**
+ * Upload files using standard HTML forms.
+ * @see BxDolUploader
+ */
+class BxBaseUploaderSimple extends BxDolUploader
+{
+    protected $_sIframeId;
+
+    function __construct ($aObject, $sStorageObject, $sUniqId)
+    {
+        parent::__construct($aObject, $sStorageObject, $sUniqId);
+        $this->_sIframeId = 'bx-form-input-files-' . $sUniqId . '-iframe';
+        $this->_sButtonTemplate = 'uploader_button_simple.html';
+    }
+
+    /**
+     * Get uploader button title
+     */
+    public function getUploaderButtonTitle($mixed = false)
+    {
+        if (is_string($mixed))
+            return $mixed;
+        elseif (is_array($mixed) && isset($mixed['Simple']))
+            return $mixed['Simple'];
+        else
+            return _t('_sys_uploader_simple_button_name');
+    }
+
+    /**
+     * Show uploader form.
+     * @return HTML string
+     */
+    public function getUploaderForm($isMultiple = true, $iContentId = false)
+    {
+        return $this->_oTemplate->parseHtmlByName('uploader_form_simple.html', array(
+            'form_container_id' => $this->_sFormContainerId,
+            'errors_container_id' => $this->_sErrorsContainerId,
+            'uploader_instance_name' => $this->getNameJsInstanceUploader(),
+            'restrictions_text' => $this->getRestrictionsText(),
+            'iframe_id' => $this->_sIframeId,
+            'engine' => $this->_aObject['object'],
+            'storage_object' => $this->_sStorageObject,
+            'uniq_id' => $this->_sUniqId,
+            'multiple' => $isMultiple,
+            'content_id' => $iContentId,
+        ));
+    }
+
+}
+
+/** @} */

+ 10 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/search.html

@@ -0,0 +1,10 @@
+
+<div id="bx-sliding-menu-search" class="bx-sliding-menu-main bx-def-z-index-nav bx-def-color-bg-block bx-search-site" style="display:none; height:100%; opacity:1;">
+    <a class="bx-sliding-menu-close bx-def-margin bx-def-font-h1" href="javascript:void(0);" onclick="bx_menu_slide('#bx-sliding-menu-search')"><i class="sys-icon times"></i></a>
+    <div class="bx-def-padding">
+        <div class="bx-def-font-h1 bx-def-margin-bottom">&nbsp;</div>
+        __search_form__
+        __results__
+    </div>
+</div>
+

+ 43 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/uploader_form_html5.html

@@ -0,0 +1,43 @@
+<div id="__form_container_id__" class="bx-uploader-form-cont">
+    <div class="bx-def-padding-sec-bottom">__restrictions_text__</div>
+    <div id="__errors_container_id__"></div>
+    <div id="__div_id__"></div>
+</div>
+<script>
+    __uploader_instance_name__.initUploader({
+        template: '<div class="bx-uploader-html5">' + 
+                '<ul class="bx-uploader-html5-list bx-def-margin-bottom"></ul>' +
+                '<div class="bx-btn bx-btn-primary bx-uploader-html5-button bx-def-margin-sec-right"><bx_text_js:_sys_uploader_html5_shoose_file /></div>' + 
+                '<div class="bx-btn" onclick="__uploader_instance_name__.onClickCancel()"><bx_text:_sys_uploader_close /></div>' + 
+                '<div class="bx-clear">&nbsp;</div>' +  
+                '<div class="bx-uploader-html5-drop-area bx-def-margin-sec-top bx-def-z-index-front"><bx_text_js:_sys_uploader_html5_drop_area /></div>' +
+             '</div>',
+
+        // template for one item in file list
+        fileTemplate: '<li>' +
+                '<span class="bx-uploader-html5-file"></span>' +
+                '<span class="bx-uploader-html5-spinner"></span>' +
+                '<span class="bx-uploader-html5-size"></span>' +
+                '<a class="bx-uploader-html5-cancel" href="#"><bx_text_js:_Cancel /></a>' +
+                '<span class="bx-uploader-html5-failed-text"><bx_text_js:_Failed /></span>' +
+            '</li>',
+        
+        classes: {
+            // used to get elements from templates
+            button: 'bx-uploader-html5-button',
+            drop: 'bx-uploader-html5-drop-area',
+            dropActive: 'bx-uploader-html5-drop-area-active',
+            list: 'bx-uploader-html5-list',
+                        
+            file: 'bx-uploader-html5-file',
+            spinner: 'bx-uploader-html5-spinner',
+            size: 'bx-uploader-html5-size',
+            cancel: 'bx-uploader-html5-cancel',
+
+            // added to list item when upload completes
+            // used in css to hide progress spinner
+            success: 'bx-uploader-html5-success',
+            fail: 'bx-uploader-html5-fail'
+        }
+    });
+</script>

+ 39 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files/template/uploader_form_simple.html

@@ -0,0 +1,39 @@
+<div id="__form_container_id__" class="bx-uploader-form-cont">
+
+    <form enctype="multipart/form-data" method="POST" target="__iframe_id__" action="<bx_url_root />storage_uploader.php" onsubmit="__uploader_instance_name__.onBeforeUpload(this);">
+
+        <div class="bx-uploader-loading">
+        </div>
+
+        <div class="bx-uploader-files-list">
+
+            <div class="bx-def-padding-sec-bottom">__restrictions_text__</div>
+
+            <div class="bx-def-padding-sec-bottom" id="__errors_container_id__"></div>
+
+            <div class="bx-uploader-simple-file"><input name="f[]" type="file" /></div>
+
+            <div class="bx-uploader-add-more-files"><a href="javascript:void(0);" onclick="$('#__form_container_id__ .bx-uploader-simple-file').first().clone().insertBefore(this)"><bx_text:_sys_uploader_simple_attach_one_more_file /></a></div>
+
+            <input name="uo" value="__engine__" type="hidden" /> 
+            <input name="so" value="__storage_object__" type="hidden" /> 
+            <input name="uid" value="__uniq_id__" type="hidden" /> 
+            <input name="m" value="__multiple__" type="hidden" /> 
+            <input name="c" value="__content_id__" type="hidden" /> 
+            <input name="a" value="upload" type="hidden" /> 
+
+        </div>
+        
+        <div class="bx-def-padding-top bx-clearfix">
+
+            <div class="bx-btn bx-btn-primary bx-def-margin-sec-right" onclick="$('#__form_container_id__ form').submit();"><bx_text:_sys_uploader_simple_submit_button /></div> 
+
+            <div class="bx-btn" onclick="__uploader_instance_name__.onClickCancel()"><bx_text:_sys_uploader_close /></div>
+
+        </div>
+
+    </form>
+
+    <iframe name="__iframe_id__" id="__iframe_id__" style="width:0px; height:0px; border:0px; float:left;"></iframe>
+
+</div>

+ 8 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/files_delete.php

@@ -0,0 +1,8 @@
+<?php
+
+$aFilesDelete = array(
+    'template/images/icons/exclamation-small-red.png',
+    'template/images/icons/information-small-white.png',
+    'template/menu_site.html',
+);
+

+ 2 - 0
upgrade/files/8.0.0.A7-8.0.0.A8/script.php

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

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels